From a4242443f1c620b271bb0acfa5900e2a549056bb Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 12:03:35 +0300 Subject: [PATCH 01/13] added new --output-dir flag to audit command + added insertion with validation to auditCmd --- cli/docs/flags.go | 4 +++- cli/scancommands.go | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/cli/docs/flags.go b/cli/docs/flags.go index 3297c36a..56179289 100644 --- a/cli/docs/flags.go +++ b/cli/docs/flags.go @@ -116,6 +116,7 @@ const ( ThirdPartyContextualAnalysis = "third-party-contextual-analysis" RequirementsFile = "requirements-file" WorkingDirs = "working-dirs" + OutputDir = "output-dir" // Unique curation flags CurationOutput = "curation-format" @@ -152,7 +153,7 @@ var commandFlags = map[string][]string{ url, user, password, accessToken, ServerId, InsecureTls, Project, Watches, RepoPath, Licenses, OutputFormat, ExcludeTestDeps, useWrapperAudit, DepType, RequirementsFile, Fail, ExtendedTable, WorkingDirs, ExclusionsAudit, Mvn, Gradle, Npm, Pnpm, Yarn, Go, Nuget, Pip, Pipenv, Poetry, MinSeverity, FixableOnly, ThirdPartyContextualAnalysis, Threads, - Sca, Iac, Sast, Secrets, WithoutCA, ScanVuln, + Sca, Iac, Sast, Secrets, WithoutCA, ScanVuln, OutputDir, }, CurationAudit: { CurationOutput, WorkingDirs, Threads, RequirementsFile, @@ -228,6 +229,7 @@ var flagsMap = map[string]components.Flag{ components.WithBoolDefaultValue(true), ), WorkingDirs: components.NewStringFlag(WorkingDirs, "A comma-separated list of relative working directories, to determine audit targets locations."), + OutputDir: components.NewStringFlag(OutputDir, "Some description"), ExclusionsAudit: components.NewStringFlag( Exclusions, "List of exclusions separated by semicolons, utilized to skip sub-projects from undergoing an audit. These exclusions may incorporate the * and ? wildcards.", diff --git a/cli/scancommands.go b/cli/scancommands.go index 17ea3e9a..d54d4f9d 100644 --- a/cli/scancommands.go +++ b/cli/scancommands.go @@ -19,6 +19,7 @@ import ( "github.com/jfrog/jfrog-cli-security/commands/enrich" "github.com/jfrog/jfrog-cli-security/utils/xray" "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/urfave/cli" "os" @@ -455,6 +456,11 @@ func CreateAuditCmd(c *components.Context) (*audit.AuditCommand, error) { if err != nil { return nil, err } + scansOutputDir, err := getAndValidateOutputDirExistsIfProvided(c) + if err != nil { + return nil, err + } + auditCmd.SetAnalyticsMetricsService(xsc.NewAnalyticsMetricsService(serverDetails)) auditCmd.SetTargetRepoPath(addTrailingSlashToRepoPathIfNeeded(c)). @@ -465,7 +471,8 @@ func CreateAuditCmd(c *components.Context) (*audit.AuditCommand, error) { SetPrintExtendedTable(c.GetBoolFlagValue(flags.ExtendedTable)). SetMinSeverityFilter(minSeverity). SetFixableOnly(c.GetBoolFlagValue(flags.FixableOnly)). - SetThirdPartyApplicabilityScan(c.GetBoolFlagValue(flags.ThirdPartyContextualAnalysis)) + SetThirdPartyApplicabilityScan(c.GetBoolFlagValue(flags.ThirdPartyContextualAnalysis)). + SetScansResultsOutputDir(scansOutputDir) if c.GetStringFlagValue(flags.Watches) != "" { auditCmd.SetWatches(splitByCommaAndTrim(c.GetStringFlagValue(flags.Watches))) @@ -629,6 +636,21 @@ func getCurationCommand(c *components.Context) (*curation.CurationAuditCommand, return curationAuditCommand, nil } +func getAndValidateOutputDirExistsIfProvided(c *components.Context) (string, error) { + scansOutputDir := c.GetStringFlagValue(flags.OutputDir) + if scansOutputDir == "" { + return "", nil + } + exists, err := fileutils.IsDirExists(scansOutputDir, false) + if err != nil { + return "", err + } + if !exists { + return "", errors.New(fmt.Sprintf("output directory path for saving scans results was provided, but the directory doesn't exist: '%s'", scansOutputDir)) + } + return scansOutputDir, nil +} + func DockerScanMockCommand() components.Command { // Mock how the CLI handles docker commands: // https://github.com/jfrog/jfrog-cli/blob/v2/buildtools/cli.go#L691 From 311229354f98c09668f37431c83d12638b0a9243 Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 12:04:55 +0300 Subject: [PATCH 02/13] added scans output dir to auditParams and passed it to the scanners tasks --- commands/audit/audit.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/commands/audit/audit.go b/commands/audit/audit.go index 016ff212..bd9ad907 100644 --- a/commands/audit/audit.go +++ b/commands/audit/audit.go @@ -3,19 +3,17 @@ package audit import ( "errors" "fmt" - jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-security/jas" "github.com/jfrog/jfrog-cli-security/jas/applicability" "github.com/jfrog/jfrog-cli-security/jas/runner" "github.com/jfrog/jfrog-cli-security/jas/secrets" + "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/xray/scangraph" "github.com/jfrog/jfrog-cli-security/utils/xsc" - "github.com/jfrog/jfrog-cli-security/jas" - "github.com/jfrog/jfrog-cli-security/utils" - xrayutils "github.com/jfrog/jfrog-cli-security/utils/xray" clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/xray" @@ -115,7 +113,8 @@ func (auditCmd *AuditCommand) Run() (err error) { SetGraphBasicParams(auditCmd.AuditBasicParams). SetCommonGraphScanParams(auditCmd.CreateCommonGraphScanParams()). SetThirdPartyApplicabilityScan(auditCmd.thirdPartyApplicabilityScan). - SetThreads(auditCmd.Threads) + SetThreads(auditCmd.Threads). + SetScansResultsOutputDir(auditCmd.scanResultsOutputDir) auditParams.SetIsRecursiveScan(isRecursiveScan).SetExclusions(auditCmd.Exclusions()) auditResults, err := RunAudit(auditParams) @@ -256,7 +255,7 @@ func downloadAnalyzerManagerAndRunScanners(auditParallelRunner *utils.SecurityPa if err != nil { return fmt.Errorf("failed to create jas scanner: %s", err.Error()) } - if err = runner.AddJasScannersTasks(auditParallelRunner, scanResults, auditParams.DirectDependencies(), serverDetails, auditParams.thirdPartyApplicabilityScan, scanner, applicability.ApplicabilityScannerType, secrets.SecretsScannerType, auditParallelRunner.AddErrorToChan, auditParams.ScansToPerform(), auditParams.configProfile); err != nil { + if err = runner.AddJasScannersTasks(auditParallelRunner, scanResults, auditParams.DirectDependencies(), serverDetails, auditParams.thirdPartyApplicabilityScan, scanner, applicability.ApplicabilityScannerType, secrets.SecretsScannerType, auditParallelRunner.AddErrorToChan, auditParams.ScansToPerform(), auditParams.configProfile, auditParams.scanResultsOutputDir); err != nil { return fmt.Errorf("%s failed to run JAS scanners: %s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) } return From a6b4f3c233d1589347b860db1931af719cbaeb2a Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 12:05:19 +0300 Subject: [PATCH 03/13] added scans output dir to auditParams and passed it to the scanners tasks --- commands/audit/auditparams.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/commands/audit/auditparams.go b/commands/audit/auditparams.go index 474bf9b3..a2f34678 100644 --- a/commands/audit/auditparams.go +++ b/commands/audit/auditparams.go @@ -21,6 +21,7 @@ type AuditParams struct { thirdPartyApplicabilityScan bool threads int configProfile *clientservices.ConfigProfile + scanResultsOutputDir string } func NewAuditParams() *AuditParams { @@ -99,6 +100,11 @@ func (params *AuditParams) SetConfigProfile(configProfile *clientservices.Config return params } +func (params *AuditParams) SetScansResultsOutputDir(outputDir string) *AuditParams { + params.scanResultsOutputDir = outputDir + return params +} + func (params *AuditParams) createXrayGraphScanParams() *services.XrayGraphScanParams { return &services.XrayGraphScanParams{ RepoPath: params.commonGraphScanParams.RepoPath, From 7e2e68a14e7f593be1e02f25d52e4571a4fdeaa4 Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 12:16:19 +0300 Subject: [PATCH 04/13] added a Dump Results functionality for Sca and Jas scanners and fixed all changes signatures --- commands/audit/scarunner.go | 2 +- commands/scan/scan.go | 2 +- jas/runner/jasrunner.go | 26 +++++++++++++++----------- utils/utils.go | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/commands/audit/scarunner.go b/commands/audit/scarunner.go index 14d203d6..7671de02 100644 --- a/commands/audit/scarunner.go +++ b/commands/audit/scarunner.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/jfrog/build-info-go/utils/pythonutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "golang.org/x/exp/slices" @@ -153,6 +152,7 @@ func executeScaScanTask(auditParallelRunner *utils.SecurityParallelRunner, serve auditParallelRunner.ResultsMu.Lock() addThirdPartyDependenciesToParams(auditParams, scan.Technology, treeResult.FlatTree, treeResult.FullDepTrees) scan.XrayResults = append(scan.XrayResults, scanResults...) + err = utils.DumpScanResultsToFileIfNeeded(scanResults, auditParams.scanResultsOutputDir, "Sca") auditParallelRunner.ResultsMu.Unlock() return } diff --git a/commands/scan/scan.go b/commands/scan/scan.go index a3d913f5..c6f6d83b 100644 --- a/commands/scan/scan.go +++ b/commands/scan/scan.go @@ -450,7 +450,7 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, entitledFo log.Error(fmt.Sprintf("failed to create jas scanner: %s", err.Error())) indexedFileErrors[threadId] = append(indexedFileErrors[threadId], formats.SimpleJsonError{FilePath: filePath, ErrorMessage: err.Error()}) } - err = runner.AddJasScannersTasks(jasFileProducerConsumer, &scanResults, &depsList, scanCmd.serverDetails, false, scanner, applicability.ApplicabilityDockerScanScanType, secrets.SecretsScannerDockerScanType, jasErrHandlerFunc, utils.GetAllSupportedScans(), nil) + err = runner.AddJasScannersTasks(jasFileProducerConsumer, &scanResults, &depsList, scanCmd.serverDetails, false, scanner, applicability.ApplicabilityDockerScanScanType, secrets.SecretsScannerDockerScanType, jasErrHandlerFunc, utils.GetAllSupportedScans(), nil, "") if err != nil { log.Error(fmt.Sprintf("scanning '%s' failed with error: %s", graph.Id, err.Error())) indexedFileErrors[threadId] = append(indexedFileErrors[threadId], formats.SimpleJsonError{FilePath: filePath, ErrorMessage: err.Error()}) diff --git a/jas/runner/jasrunner.go b/jas/runner/jasrunner.go index 3e284310..d6f7b5d6 100644 --- a/jas/runner/jasrunner.go +++ b/jas/runner/jasrunner.go @@ -20,7 +20,7 @@ import ( func AddJasScannersTasks(securityParallelRunner *utils.SecurityParallelRunner, scanResults *utils.Results, directDependencies *[]string, serverDetails *config.ServerDetails, thirdPartyApplicabilityScan bool, scanner *jas.JasScanner, scanType applicability.ApplicabilityScanType, - secretsScanType secrets.SecretsScanType, errHandlerFunc func(error), scansToPreform []utils.SubScanType, configProfile *services.ConfigProfile) (err error) { + secretsScanType secrets.SecretsScanType, errHandlerFunc func(error), scansToPreform []utils.SubScanType, configProfile *services.ConfigProfile, scansOutputDir string) (err error) { if serverDetails == nil || len(serverDetails.Url) == 0 { log.Warn("To include 'Advanced Security' scan as part of the audit output, please run the 'jf c add' command before running this command.") return @@ -41,18 +41,18 @@ func AddJasScannersTasks(securityParallelRunner *utils.SecurityParallelRunner, s // This code section is related to CentralizedConfig integration in CI Next. log.Debug(fmt.Sprintf("Using config profile '%s' to determine whether to run secrets scan...", configProfile.ProfileName)) if configProfile.Modules[0].ScanConfig.SecretsScannerConfig.EnableSecretsScan { - err = addModuleJasScanTask(jfrogappsconfig.Module{}, jasutils.Secrets, securityParallelRunner, runSecretsScan(securityParallelRunner, scanner, scanResults.ExtendedScanResults, module, secretsScanType), errHandlerFunc) + err = addModuleJasScanTask(jfrogappsconfig.Module{}, jasutils.Secrets, securityParallelRunner, runSecretsScan(securityParallelRunner, scanner, scanResults.ExtendedScanResults, module, secretsScanType, scansOutputDir), errHandlerFunc) } else { log.Debug(fmt.Sprintf("Skipping secrets scan as requested by '%s' config profile...", configProfile.ProfileName)) } - } else if err = addModuleJasScanTask(module, jasutils.Secrets, securityParallelRunner, runSecretsScan(securityParallelRunner, scanner, scanResults.ExtendedScanResults, module, secretsScanType), errHandlerFunc); err != nil { + } else if err = addModuleJasScanTask(module, jasutils.Secrets, securityParallelRunner, runSecretsScan(securityParallelRunner, scanner, scanResults.ExtendedScanResults, module, secretsScanType, scansOutputDir), errHandlerFunc); err != nil { return } if runAllScanners { if configProfile == nil { if len(scansToPreform) > 0 && !slices.Contains(scansToPreform, utils.IacScan) { log.Debug("Skipping Iac scan as requested by input...") - } else if err = addModuleJasScanTask(module, jasutils.IaC, securityParallelRunner, runIacScan(securityParallelRunner, scanner, scanResults.ExtendedScanResults, module), errHandlerFunc); err != nil { + } else if err = addModuleJasScanTask(module, jasutils.IaC, securityParallelRunner, runIacScan(securityParallelRunner, scanner, scanResults.ExtendedScanResults, module, scansOutputDir), errHandlerFunc); err != nil { return } } @@ -61,11 +61,11 @@ func AddJasScannersTasks(securityParallelRunner *utils.SecurityParallelRunner, s } else if configProfile != nil { log.Debug(fmt.Sprintf("Using config profile '%s' to determine whether to run Sast scan...", configProfile.ProfileName)) if configProfile.Modules[0].ScanConfig.SastScannerConfig.EnableSastScan { - err = addModuleJasScanTask(jfrogappsconfig.Module{}, jasutils.Sast, securityParallelRunner, runSastScan(securityParallelRunner, scanner, scanResults.ExtendedScanResults, module), errHandlerFunc) + err = addModuleJasScanTask(jfrogappsconfig.Module{}, jasutils.Sast, securityParallelRunner, runSastScan(securityParallelRunner, scanner, scanResults.ExtendedScanResults, module, scansOutputDir), errHandlerFunc) } else { log.Debug(fmt.Sprintf("Skipping Sast scan as requested by '%s' config profile...", configProfile.ProfileName)) } - } else if err = addModuleJasScanTask(module, jasutils.Sast, securityParallelRunner, runSastScan(securityParallelRunner, scanner, scanResults.ExtendedScanResults, module), errHandlerFunc); err != nil { + } else if err = addModuleJasScanTask(module, jasutils.Sast, securityParallelRunner, runSastScan(securityParallelRunner, scanner, scanResults.ExtendedScanResults, module, scansOutputDir), errHandlerFunc); err != nil { return } } @@ -82,7 +82,7 @@ func AddJasScannersTasks(securityParallelRunner *utils.SecurityParallelRunner, s return err } for _, module := range scanner.JFrogAppsConfig.Modules { - if err = addModuleJasScanTask(module, jasutils.Applicability, securityParallelRunner, runContextualScan(securityParallelRunner, scanner, scanResults, module, directDependencies, thirdPartyApplicabilityScan, scanType), errHandlerFunc); err != nil { + if err = addModuleJasScanTask(module, jasutils.Applicability, securityParallelRunner, runContextualScan(securityParallelRunner, scanner, scanResults, module, directDependencies, thirdPartyApplicabilityScan, scanType, scansOutputDir), errHandlerFunc); err != nil { return } } @@ -101,7 +101,7 @@ func addModuleJasScanTask(module jfrogappsconfig.Module, scanType jasutils.JasSc } func runSecretsScan(securityParallelRunner *utils.SecurityParallelRunner, scanner *jas.JasScanner, extendedScanResults *utils.ExtendedScanResults, - module jfrogappsconfig.Module, secretsScanType secrets.SecretsScanType) parallel.TaskFunc { + module jfrogappsconfig.Module, secretsScanType secrets.SecretsScanType, scansOutputDir string) parallel.TaskFunc { return func(threadId int) (err error) { defer func() { securityParallelRunner.JasScannersWg.Done() @@ -112,13 +112,14 @@ func runSecretsScan(securityParallelRunner *utils.SecurityParallelRunner, scanne } securityParallelRunner.ResultsMu.Lock() extendedScanResults.SecretsScanResults = append(extendedScanResults.SecretsScanResults, results...) + err = utils.DumpScanResultsToFileIfNeeded(results, scansOutputDir, jasutils.Secrets.String()) securityParallelRunner.ResultsMu.Unlock() return } } func runIacScan(securityParallelRunner *utils.SecurityParallelRunner, scanner *jas.JasScanner, extendedScanResults *utils.ExtendedScanResults, - module jfrogappsconfig.Module) parallel.TaskFunc { + module jfrogappsconfig.Module, scansOutputDir string) parallel.TaskFunc { return func(threadId int) (err error) { defer func() { securityParallelRunner.JasScannersWg.Done() @@ -129,13 +130,14 @@ func runIacScan(securityParallelRunner *utils.SecurityParallelRunner, scanner *j } securityParallelRunner.ResultsMu.Lock() extendedScanResults.IacScanResults = append(extendedScanResults.IacScanResults, results...) + err = utils.DumpScanResultsToFileIfNeeded(results, scansOutputDir, jasutils.IaC.String()) securityParallelRunner.ResultsMu.Unlock() return } } func runSastScan(securityParallelRunner *utils.SecurityParallelRunner, scanner *jas.JasScanner, extendedScanResults *utils.ExtendedScanResults, - module jfrogappsconfig.Module) parallel.TaskFunc { + module jfrogappsconfig.Module, scansOutputDir string) parallel.TaskFunc { return func(threadId int) (err error) { defer func() { securityParallelRunner.JasScannersWg.Done() @@ -146,13 +148,14 @@ func runSastScan(securityParallelRunner *utils.SecurityParallelRunner, scanner * } securityParallelRunner.ResultsMu.Lock() extendedScanResults.SastScanResults = append(extendedScanResults.SastScanResults, results...) + err = utils.DumpScanResultsToFileIfNeeded(results, scansOutputDir, jasutils.Sast.String()) securityParallelRunner.ResultsMu.Unlock() return } } func runContextualScan(securityParallelRunner *utils.SecurityParallelRunner, scanner *jas.JasScanner, scanResults *utils.Results, - module jfrogappsconfig.Module, directDependencies *[]string, thirdPartyApplicabilityScan bool, scanType applicability.ApplicabilityScanType) parallel.TaskFunc { + module jfrogappsconfig.Module, directDependencies *[]string, thirdPartyApplicabilityScan bool, scanType applicability.ApplicabilityScanType, scansOutputDir string) parallel.TaskFunc { return func(threadId int) (err error) { defer func() { securityParallelRunner.JasScannersWg.Done() @@ -165,6 +168,7 @@ func runContextualScan(securityParallelRunner *utils.SecurityParallelRunner, sca } securityParallelRunner.ResultsMu.Lock() scanResults.ExtendedScanResults.ApplicabilityScanResults = append(scanResults.ExtendedScanResults.ApplicabilityScanResults, results...) + err = utils.DumpScanResultsToFileIfNeeded(results, scansOutputDir, jasutils.Applicability.String()) securityParallelRunner.ResultsMu.Unlock() return } diff --git a/utils/utils.go b/utils/utils.go index 1b4d2da7..c1655b91 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -3,8 +3,14 @@ package utils import ( "crypto" "encoding/hex" + "encoding/json" "fmt" + "github.com/jfrog/jfrog-client-go/utils/log" + "os" + "path/filepath" + "reflect" "strings" + "time" ) const ( @@ -119,3 +125,30 @@ func splitEnvVar(envVar string) (key, value string) { } return split[0], strings.Join(split[1:], "=") } + +// If an output dir was provided through --output-dir flag, we create in the provided path, a new directory with a new file containing the scan results +// This functionallity is being used for Jas scanners and SCA scanner +func DumpScanResultsToFileIfNeeded(results interface{}, scanResultsOutputDir string, scanType string) (err error) { + // TODO this function should be in utils/results/results.go after the refactor, since it is a common code for Jas and SCA scanners + // TODO AFTER merging the refactor - make sure to create a new directory for every Scan Target and put all its results in this dir, for every Target + if scanResultsOutputDir == "" || reflect.ValueOf(results).IsNil() { + return + } + log.Debug(fmt.Sprintf("Scans output directory was provided, saving %s scan results to file...", scanType)) + + fileContent, err := json.Marshal(results) + if err != nil { + return fmt.Errorf("failed to write %s scan results to file: %s", scanType, err.Error()) + } + + var curTimeHash string + if curTimeHash, err = Md5Hash(time.Now().String()); err != nil { + return fmt.Errorf("failed to write %s scan results to file: %s", scanType, err.Error()) + } + + resultsFileName := strings.ToLower(scanType) + "_results_" + curTimeHash + ".json" + if err = os.WriteFile(filepath.Join(scanResultsOutputDir, resultsFileName), fileContent, 0644); err != nil { + return fmt.Errorf("failed to write %s scan results to file: %s", scanType, err.Error()) + } + return +} From 5dba209c34a8780af6585b6b353ef4a118e13e5a Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 12:22:23 +0300 Subject: [PATCH 05/13] sets the new flag as hidden flag --- cli/docs/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/docs/flags.go b/cli/docs/flags.go index 56179289..06fa6b27 100644 --- a/cli/docs/flags.go +++ b/cli/docs/flags.go @@ -229,7 +229,7 @@ var flagsMap = map[string]components.Flag{ components.WithBoolDefaultValue(true), ), WorkingDirs: components.NewStringFlag(WorkingDirs, "A comma-separated list of relative working directories, to determine audit targets locations."), - OutputDir: components.NewStringFlag(OutputDir, "Some description"), + OutputDir: components.NewStringFlag(OutputDir, "Some description", components.SetHiddenStrFlag()), ExclusionsAudit: components.NewStringFlag( Exclusions, "List of exclusions separated by semicolons, utilized to skip sub-projects from undergoing an audit. These exclusions may incorporate the * and ? wildcards.", From a3dd16026338c5a015d157376fd928b07c9fa4d9 Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 14:54:22 +0300 Subject: [PATCH 06/13] minor test fix --- jas/runner/jasrunner_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jas/runner/jasrunner_test.go b/jas/runner/jasrunner_test.go index 4e2a3a06..72ca13d5 100644 --- a/jas/runner/jasrunner_test.go +++ b/jas/runner/jasrunner_test.go @@ -39,7 +39,7 @@ func TestGetExtendedScanResults_ServerNotValid(t *testing.T) { scanner := &jas.JasScanner{} jasScanner, err := jas.CreateJasScanner(scanner, nil, &jas.FakeServerDetails, jas.GetAnalyzerManagerXscEnvVars("", scanResults.GetScaScannedTechnologies()...)) assert.NoError(t, err) - err = AddJasScannersTasks(securityParallelRunnerForTest, scanResults, &[]string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, nil, false, jasScanner, applicability.ApplicabilityScannerType, secrets.SecretsScannerType, securityParallelRunnerForTest.AddErrorToChan, utils.GetAllSupportedScans(), nil) + err = AddJasScannersTasks(securityParallelRunnerForTest, scanResults, &[]string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, nil, false, jasScanner, applicability.ApplicabilityScannerType, secrets.SecretsScannerType, securityParallelRunnerForTest.AddErrorToChan, utils.GetAllSupportedScans(), nil, "") assert.NoError(t, err) } From 931748065aaf6fb53e5e76255a5c8365c6e33acc Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 15:00:20 +0300 Subject: [PATCH 07/13] minor test fix --- cli/scancommands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/scancommands.go b/cli/scancommands.go index d54d4f9d..aaac4152 100644 --- a/cli/scancommands.go +++ b/cli/scancommands.go @@ -646,7 +646,7 @@ func getAndValidateOutputDirExistsIfProvided(c *components.Context) (string, err return "", err } if !exists { - return "", errors.New(fmt.Sprintf("output directory path for saving scans results was provided, but the directory doesn't exist: '%s'", scansOutputDir)) + return "", fmt.Errorf("output directory path for saving scans results was provided, but the directory doesn't exist: '%s'", scansOutputDir) } return scansOutputDir, nil } From 55356ea70c043e3ca1b20550c4e9e6d697308a65 Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 18:41:00 +0300 Subject: [PATCH 08/13] integration test added to Audit with output dir + improved another test in the same file --- commands/audit/audit_test.go | 79 ++- .../graphScanResults/graphScanResult.txt | 534 ++++++++++++++++++ utils/test_mocks.go | 14 +- utils/utils.go | 2 +- 4 files changed, 614 insertions(+), 15 deletions(-) create mode 100644 tests/testdata/other/graphScanResults/graphScanResult.txt diff --git a/commands/audit/audit_test.go b/commands/audit/audit_test.go index 1a1b0acc..6ab50e96 100644 --- a/commands/audit/audit_test.go +++ b/commands/audit/audit_test.go @@ -6,11 +6,12 @@ import ( coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/xray/scangraph" - clientTests "github.com/jfrog/jfrog-client-go/utils/tests" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + scanservices "github.com/jfrog/jfrog-client-go/xray/services" "github.com/jfrog/jfrog-client-go/xsc/services" "github.com/stretchr/testify/assert" - "os" "path/filepath" + "strings" "testing" ) @@ -95,6 +96,11 @@ func TestAuditWithConfigProfile(t *testing.T) { mockServer, serverDetails := utils.XrayServer(t, utils.EntitlementsMinVersion) defer mockServer.Close() + tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) + defer createTempDirCallback() + testDirPath := filepath.Join("..", "..", "tests", "testdata", "projects", "jas", "jas") + assert.NoError(t, biutils.CopyDir(testDirPath, tempDirPath, true, nil)) + auditBasicParams := (&utils.AuditBasicParams{}). SetServerDetails(serverDetails). SetOutputFormat(format.Table). @@ -102,28 +108,25 @@ func TestAuditWithConfigProfile(t *testing.T) { configProfile := testcase.configProfile auditParams := NewAuditParams(). + SetWorkingDirs([]string{tempDirPath}). SetGraphBasicParams(auditBasicParams). SetConfigProfile(&configProfile). SetCommonGraphScanParams(&scangraph.CommonGraphScanParams{ RepoPath: "", - ProjectKey: "", - Watches: nil, - ScanType: "dependency", + ScanType: scanservices.Dependency, IncludeVulnerabilities: true, XscVersion: services.ConfigProfileMinXscVersion, MultiScanId: "random-msi", }) auditParams.SetIsRecursiveScan(true) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - testDirPath := filepath.Join("..", "..", "tests", "testdata", "projects", "jas", "jas") - assert.NoError(t, biutils.CopyDir(testDirPath, tempDirPath, true, nil)) + /* + baseWd, err := os.Getwd() + assert.NoError(t, err) + chdirCallback := clientTests.ChangeDirWithCallback(t, baseWd, tempDirPath) + defer chdirCallback() - baseWd, err := os.Getwd() - assert.NoError(t, err) - chdirCallback := clientTests.ChangeDirWithCallback(t, baseWd, tempDirPath) - defer chdirCallback() + */ auditResults, err := RunAudit(auditParams) assert.NoError(t, err) @@ -149,3 +152,53 @@ func TestAuditWithConfigProfile(t *testing.T) { }) } } + +// This test tests audit flow when providing --output-dir flag +func TestAuditWithScansOutputDir(t *testing.T) { + // TODO this test needs to be improved to verify the files content after we use the Sarif convertor and write Sarif content into the files + mockServer, serverDetails := utils.XrayServer(t, utils.EntitlementsMinVersion) + defer mockServer.Close() + + outputDirPath, removeOutputDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) + defer removeOutputDirCallback() + + tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) + defer createTempDirCallback() + testDirPath := filepath.Join("..", "..", "tests", "testdata", "projects", "jas", "jas") + assert.NoError(t, biutils.CopyDir(testDirPath, tempDirPath, true, nil)) + + auditBasicParams := (&utils.AuditBasicParams{}). + SetServerDetails(serverDetails). + SetOutputFormat(format.Table). + SetUseJas(true) + + auditParams := NewAuditParams(). + SetWorkingDirs([]string{tempDirPath}). + SetGraphBasicParams(auditBasicParams). + SetCommonGraphScanParams(&scangraph.CommonGraphScanParams{ + ScanType: scanservices.Dependency, + IncludeVulnerabilities: true, + MultiScanId: utils.TestScaScanId, + }). + SetScansResultsOutputDir(outputDirPath) + auditParams.SetIsRecursiveScan(true) + + _, err := RunAudit(auditParams) + assert.NoError(t, err) + + filesList, err := fileutils.ListFiles(outputDirPath, false) + assert.Len(t, filesList, 5) + + var fileNamesWithoutSuffix []string + for _, fileName := range filesList { + // Removing .json suffix to so we can check by suffix all expected files exist + splitName := strings.Split(fileName, "_") + fileNamesWithoutSuffix = append(fileNamesWithoutSuffix, splitName[0]) + } + + assert.Contains(t, fileNamesWithoutSuffix, filepath.Join(outputDirPath, "sca")) + assert.Contains(t, fileNamesWithoutSuffix, filepath.Join(outputDirPath, "iac")) + assert.Contains(t, fileNamesWithoutSuffix, filepath.Join(outputDirPath, "sast")) + assert.Contains(t, fileNamesWithoutSuffix, filepath.Join(outputDirPath, "secrets")) + assert.Contains(t, fileNamesWithoutSuffix, filepath.Join(outputDirPath, "applicability")) +} diff --git a/tests/testdata/other/graphScanResults/graphScanResult.txt b/tests/testdata/other/graphScanResults/graphScanResult.txt new file mode 100644 index 00000000..c806b510 --- /dev/null +++ b/tests/testdata/other/graphScanResults/graphScanResult.txt @@ -0,0 +1,534 @@ +{ + "scan_id" : "a6453052-5024-4534-7326-f15c697162eb", + "component_id" : "root", + "status" : "completed", + "package_type" : "generic", + "top_vuln_severity" : "Critical", + "progress_percentage" : 100, + "vulnerabilities" : [ { + "severity" : "Low", + "summary" : "When installing a package from a Mercurial VCS URL (ie \"pip install \nhg+...\") with pip prior to v23.3, the specified Mercurial revision could\n be used to inject arbitrary configuration options to the \"hg clone\" \ncall (ie \"--config\"). Controlling the Mercurial configuration can modify\n how and which repository is installed. This vulnerability does not \naffect users who aren't installing from Mercurial.", + "issue_id" : "XRAY-534517", + "provider" : "JFrog", + "edited" : "0001-01-01T00:00:00Z", + "extended_information" : { }, + "cves" : [ { + "cve" : "CVE-2023-5752", + "cwe" : [ "CWE-77" ], + "cvss_v3_score" : "3.3", + "cvss_v3_vector" : "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N" + } ], + "components" : { + "pypi://pip:21.2.3" : { + "package_name" : "pip", + "package_version" : "21.2.3", + "package_type" : "pypi", + "fixed_versions" : [ "[23.3]" ], + "infected_versions" : [ "(,23.3)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://pip:21.2.3" + } ] ] + } + }, + "references" : [ "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/YBSB3SUPQ3VIFYUMHPO3MEQI4BJAXKCZ/", "https://mail.python.org/archives/list/security-announce@python.org/thread/F4PL35U6X4VVHZ5ILJU3PWUWN7H7LZXL", "https://github.com/pypa/pip/pull/12306", "https://github.com/pypa/pip/commit/389cb799d0da9a840749fcd14878928467ed49b4", "https://nvd.nist.gov/vuln/detail/CVE-2023-5752", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KFC2SPFG5FLCZBYY2K3T5MFW2D22NG6E/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/FXUVMJM25PUAZRQZBF54OFVKTY3MINPW/", "https://github.com/pypa/advisory-database/tree/main/vulns/pip/PYSEC-2023-228.yaml", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KFC2SPFG5FLCZBYY2K3T5MFW2D22NG6E", "https://mail.python.org/archives/list/security-announce@python.org/thread/F4PL35U6X4VVHZ5ILJU3PWUWN7H7LZXL/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/65UKKF5LBHEFDCUSPBHUN4IHYX7SRMHH/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/622OZXWG72ISQPLM5Y57YCVIMWHD4C3U/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/65UKKF5LBHEFDCUSPBHUN4IHYX7SRMHH", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/FXUVMJM25PUAZRQZBF54OFVKTY3MINPW", "https://github.com/advisories/GHSA-mq26-g339-26xf", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/622OZXWG72ISQPLM5Y57YCVIMWHD4C3U", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/YBSB3SUPQ3VIFYUMHPO3MEQI4BJAXKCZ" ] + }, { + "severity" : "High", + "summary" : "An issue was discovered in pip (all versions) because it installs the version with the highest version number, even if the user had intended to obtain a private package from a private index. This only affects use of the --extra-index-url option, and exploitation requires that the package does not already exist in the public index (and thus the attacker can put the package there with an arbitrary version number). NOTE: it has been reported that this is intended functionality and the user is responsible for using --extra-index-url securely", + "issue_id" : "XRAY-97724", + "is_high_profile" : true, + "provider" : "JFrog", + "edited" : "2023-01-08T19:31:00Z", + "extended_information" : { + "short_description" : "pip could download private packages from a public PyPI repository leading to code execution.", + "full_description" : "This CVE refers to the possibility of a \"dependency confusion\" attack when using `pip` with the `--extra-index-url` flag. This vulnerability has been disputed by the maintainers of pip as the described behavior is intended (although potentially insecure) and as such there are no plans to fix it. \n\nIf pip is executed with the `--extra-index-url` when using a private PyPI repository, an attacker could cause pip to download a private package (for example one named `private_package`) by adding a package with the same name (`private_package`) in the public PyPI repository. This would lead to remote code execution as pip will download the public package that could contain malicious code. This dependency confusion attack was [demonstrated in 2021 by Alex Birsan](https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610).", + "jfrog_research_severity" : "Medium", + "jfrog_research_severity_reasons" : [ { + "name" : "The issue has been disputed by the vendor", + "description" : "Pip maintainers, and others such as [RHEL](https://access.redhat.com/security/cve/cve-2018-20225) do not consider this a vulnerability as it is the intended behaviour", + "is_positive" : true + }, { + "name" : "The issue results in a severe impact (such as remote code execution)", + "description" : "A malicious package can contain arbitrary code, which is by definition a severe code execution impact" + }, { + "name" : "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", + "description" : "Exploitation requires the pip client to use the `--extra-index-url` flag. The attacker must research and find the names of the \"private packages\" being installed by the victim, and then publish packages with the same name (but containing a malicious payload) to PyPI.", + "is_positive" : true + } ], + "remediation" : "##### Deployment mitigations\n\nDo not use the `--extra-index-url` flag with pip and consider using version pinning for deployments." + }, + "cves" : [ { + "cve" : "CVE-2018-20225", + "cwe" : [ "CWE-20" ], + "cvss_v2_score" : "6.8", + "cvss_v2_vector" : "CVSS:2.0/AV:N/AC:M/Au:N/C:P/I:P/A:P", + "cvss_v3_score" : "7.8", + "cvss_v3_vector" : "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H" + } ], + "components" : { + "pypi://pip:21.2.3" : { + "package_name" : "pip", + "package_version" : "21.2.3", + "package_type" : "pypi", + "infected_versions" : [ "(,)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://pip:21.2.3" + } ] ] + } + }, + "references" : [ "https://pip.pypa.io/en/stable/news/", "https://lists.apache.org/thread.html/rb1adce798445facd032870d644eb39c4baaf9c4a7dd5477d12bb6ab2%40%3Cgithub.arrow.apache.org%3E", "https://cowlicks.website/posts/arbitrary-code-execution-from-pips-extra-index-url.html", "https://bugzilla.redhat.com/show_bug.cgi?id=1835736", "https://nvd.nist.gov/vuln/detail/CVE-2018-20225" ] + }, { + "severity" : "Critical", + "summary" : "A vulnerability was discovered in the PyYAML library in versions before 5.3.1, where it is susceptible to arbitrary code execution when it processes untrusted YAML files through the full_load method or with the FullLoader loader. Applications that use the library to process untrusted input may be vulnerable to this flaw. An attacker could use this flaw to execute arbitrary code on the system by abusing the python/object/new constructor.", + "issue_id" : "XRAY-95701", + "is_high_profile" : true, + "provider" : "JFrog", + "edited" : "2022-09-05T11:37:00Z", + "extended_information" : { + "short_description" : "Insufficient input validation in the PyYAML library allows unauthenticated network attackers to perform code execution when parsing a crafted YAML file.", + "full_description" : "The [PyYAML](https://pypi.org/project/PyYAML/) library is a popular Python YAML parser.\n\nAttackers can trigger the exploit by supplying a crafted YAML file to the `full_load` method or a `load` method that uses `Loader=FullLoader` (which is the default). A public [Exploit (PoC)](https://gist.github.com/adamczi/23a3b6d4bb7b2be35e79b0667d6682e1) exists which demonstrates remote code execution, making this vulnerability likely to be exploited in practice.\n\nThe library implementation has a [prototype pollution](https://shieldfy.io/security-wiki/prototype-pollution/introduction-to-prototype-pollution/) issue in the `construct_python_object_apply()` function in the `lib/yaml/constructor.py` module, used by the `full_load()` method and the `FullLoader` loader. This allows an attacker to create a property for an object created from the loaded file. The constructor does not check the attribute for conflicts; for example, it is possible to create an `extend` attribute when an `extend` method already exists in an object. This can be exploited to replace the `extend` method with the insecure `yaml.unsafe_load()` function, which is later invoked by `construct_python_object_apply()` and executes a malicious YAML payload. \n\nThe [official solution]() provides a blacklist of properties and attributes that cannot be redefined, such as the `extend` method and all special methods (`__set__`, `__setitem__`, etc.). In addition, the `README` file is updated with a request to use the `safe_load()` function and `SafeLoader` loader for all untrusted input. It is still possible to use the `UnsafeLoader`, and the fix does not completely solve the problem, as shown by the later CVE-2020-14343.\n\nThe vulnerability was discovered by [Riccardo Schirone](https://github.com/ret2libc).", + "jfrog_research_severity" : "Critical", + "jfrog_research_severity_reasons" : [ { + "name" : "The issue can be exploited by attackers over the network" + }, { + "name" : "The issue results in a severe impact (such as remote code execution)" + }, { + "name" : "The prerequisites for exploiting the issue are either extremely common or nonexistent (always exploitable)" + }, { + "name" : "The issue has an exploit published" + } ], + "remediation" : "##### Development mitigations\n\n* Use `yaml.safe_load()` or the `SafeLoader` loader for all inputs." + }, + "cves" : [ { + "cve" : "CVE-2020-1747", + "cwe" : [ "CWE-20" ], + "cvss_v2_score" : "10.0", + "cvss_v2_vector" : "CVSS:2.0/AV:N/AC:L/Au:N/C:C/I:C/A:C", + "cvss_v3_score" : "9.8", + "cvss_v3_vector" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + } ], + "components" : { + "pypi://pyyaml:5.2" : { + "package_name" : "pyyaml", + "package_version" : "5.2", + "package_type" : "pypi", + "fixed_versions" : [ "[5.3.1]" ], + "infected_versions" : [ "(,5.3.1)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://pyyaml:5.2" + } ] ] + } + }, + "references" : [ "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/MMQXSZXNJT6ERABJZAAICI3DQSQLCP3D/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MMQXSZXNJT6ERABJZAAICI3DQSQLCP3D/", "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/ZBJA3SGNJKCAYPSHOHWY3KBCWNM5NYK2/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MMQXSZXNJT6ERABJZAAICI3DQSQLCP3D", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/K5HEPD7LEVDPCITY5IMDYWXUMX37VFMY", "https://github.com/advisories/GHSA-6757-jp84-gxfx", "https://github.com/yaml/pyyaml/commit/5080ba513377b6355a0502104846ee804656f1e0", "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/K5HEPD7LEVDPCITY5IMDYWXUMX37VFMY/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/7PPAS6C4SZRDQLR7C22A5U3QOLXY33JX/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WORRFHPQVAFKKXXWLSSW6XKUYLWM6CSH", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBJA3SGNJKCAYPSHOHWY3KBCWNM5NYK2/", "https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2020-1747", "http://lists.opensuse.org/opensuse-security-announce/2020-05/msg00017.html", "https://nvd.nist.gov/vuln/detail/CVE-2020-1747", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/K5HEPD7LEVDPCITY5IMDYWXUMX37VFMY/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBJA3SGNJKCAYPSHOHWY3KBCWNM5NYK2", "https://github.com/yaml/pyyaml", "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/7PPAS6C4SZRDQLR7C22A5U3QOLXY33JX/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/7PPAS6C4SZRDQLR7C22A5U3QOLXY33JX", "https://github.com/yaml/pyyaml/pull/386", "http://lists.opensuse.org/opensuse-security-announce/2020-04/msg00017.html", "https://www.oracle.com/security-alerts/cpujul2022.html", "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/WORRFHPQVAFKKXXWLSSW6XKUYLWM6CSH/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WORRFHPQVAFKKXXWLSSW6XKUYLWM6CSH/" ] + }, { + "severity" : "Critical", + "summary" : "A vulnerability was discovered in the PyYAML library in versions before 5.4, where it is susceptible to arbitrary code execution when it processes untrusted YAML files through the full_load method or with the FullLoader loader. Applications that use the library to process untrusted input may be vulnerable to this flaw. This flaw allows an attacker to execute arbitrary code on the system by abusing the python/object/new constructor. This flaw is due to an incomplete fix for CVE-2020-1747.", + "issue_id" : "XRAY-140308", + "is_high_profile" : true, + "provider" : "JFrog", + "edited" : "2023-05-30T08:53:00Z", + "extended_information" : { + "short_description" : "An improper input validation in PyYAML when parsing untrusted YAML files can be used by an attacker to get remote code execution.", + "full_description" : "[PyYAML](https://github.com/yaml/pyyaml) is a python library used to parse YAML files.\n\nAn attacker can exploit this vulnerability for a remote code execution, when the library is used for parsing untrusted YAML files. No public exploit is available, but exploiting it for achieving remote code execution is considered trivial.\n\nAn attacker can exploit this vulnerability and execute code by adding a call to `eval` or `exec` in the `python/object/new` constructor on the crafted YAML file that gets parsed. For example -\n```yaml\n!!python/object/new:tuple \n- !!python/object/new:map \n - !!python/name:eval\n - [ print(\"RCE EXPLOIT!\") ]\n```\n\nThe vulnerability is applicable when the library's function `yaml.load()` is used with an attacker-supplied input, which by default loads a YAML file with the `FullLoader` class. This class calls the vulnerable `full_load()` function which can cause code execution when the YAML is deserialized. For example - \n```python\nimport yaml\nfile_content = request.files['file'].read()\nprint('Loading yaml file...')\nyaml.load(file_content)\n```", + "jfrog_research_severity" : "Critical", + "jfrog_research_severity_reasons" : [ { + "name" : "The issue results in a severe impact (such as remote code execution)", + "description" : "Python code injection" + }, { + "name" : "The issue is trivial to exploit and does not require a published writeup or PoC", + "description" : "Even without a public exploit, exploitation is trivial as shown in the extended description" + }, { + "name" : "The prerequisites for exploiting the issue are either extremely common or nonexistent (always exploitable)", + "description" : "It is likely that a program that uses PyYAML would pass remote input to `yaml.load()` since this is the main API of the PyYAML package" + }, { + "name" : "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", + "description" : "The attacker has to find a remote input that propagates into the `yaml.load` API", + "is_positive" : true + } ], + "remediation" : "##### Development mitigations\n\nPerform any of the following -\n1. Replace any call to `yaml.load` or `yaml.full_load` with a call to `yaml.safe_load()`\n2. Replace any usage of the `Loader` or `FullLoader` class with the `SafeLoader` class" + }, + "cves" : [ { + "cve" : "CVE-2020-14343", + "cwe" : [ "CWE-20" ], + "cvss_v2_score" : "10.0", + "cvss_v2_vector" : "CVSS:2.0/AV:N/AC:L/Au:N/C:C/I:C/A:C", + "cvss_v3_score" : "9.8", + "cvss_v3_vector" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + } ], + "components" : { + "pypi://pyyaml:5.2" : { + "package_name" : "pyyaml", + "package_version" : "5.2", + "package_type" : "pypi", + "fixed_versions" : [ "[5.4]" ], + "infected_versions" : [ "(,5.4)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://pyyaml:5.2" + } ] ] + } + }, + "references" : [ "https://github.com/yaml/pyyaml/issues/420", "https://www.oracle.com/security-alerts/cpujul2022.html", "https://github.com/yaml/pyyaml/issues/420#issuecomment-663673966", "https://www.oracle.com/security-alerts/cpuapr2022.html", "https://github.com/SeldonIO/seldon-core/issues/2252", "https://bugzilla.redhat.com/show_bug.cgi?id=1860466", "https://pypi.org/project/PyYAML/", "https://github.com/yaml/pyyaml/commit/a001f2782501ad2d24986959f0239a354675f9dc", "https://nvd.nist.gov/vuln/detail/CVE-2020-14343", "https://github.com/advisories/GHSA-8q59-q68h-6hv4", "https://github.com/yaml/pyyaml" ] + }, { + "severity" : "Low", + "summary" : "kuku", + "issue_id" : "CustomIssue_c7CBoSVCFHTcAm2e", + "provider" : "Custom", + "edited" : "2024-03-07T07:02:49Z", + "extended_information" : { }, + "components" : { + "pypi://pyyaml:5.2" : { + "package_name" : "pyyaml", + "package_version" : "5.2", + "package_type" : "pypi", + "infected_versions" : [ "[5.2]" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://pyyaml:5.2" + } ] ] + } + } + }, { + "severity" : "Low", + "summary" : "asdsad", + "issue_id" : "CustomIssue_ig6lvislh7EPKJ72", + "provider" : "Custom", + "edited" : "2024-04-02T12:40:14Z", + "extended_information" : { }, + "components" : { + "pypi://pyyaml:5.2" : { + "package_name" : "pyyaml", + "package_version" : "5.2", + "package_type" : "pypi", + "infected_versions" : [ "[5.2]" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://pyyaml:5.2" + } ] ] + } + } + }, { + "severity" : "Low", + "summary" : "szf", + "issue_id" : "CustomIssue_LX0LOklTg4TD7TNL", + "provider" : "Custom", + "edited" : "2024-04-15T10:16:31Z", + "extended_information" : { }, + "components" : { + "pypi://pyyaml:5.2" : { + "package_name" : "pyyaml", + "package_version" : "5.2", + "package_type" : "pypi", + "infected_versions" : [ "[5.2]" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://pyyaml:5.2" + } ] ] + } + } + }, { + "severity" : "Low", + "summary" : "test", + "issue_id" : "CustomIssue_K8UZUisc4ho1wuOA", + "provider" : "Custom", + "edited" : "2024-04-15T11:05:12Z", + "extended_information" : { }, + "components" : { + "pypi://pyyaml:5.2" : { + "package_name" : "pyyaml", + "package_version" : "5.2", + "package_type" : "pypi", + "infected_versions" : [ "[5.2]" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://pyyaml:5.2" + } ] ] + } + } + }, { + "severity" : "Medium", + "summary" : "Python Packaging Authority (PyPA) setuptools before 65.5.1 allows remote attackers to cause a denial of service via HTML in a crafted package or custom PackageIndex page. There is a Regular Expression Denial of Service (ReDoS) in package_index.py.", + "issue_id" : "XRAY-412522", + "is_high_profile" : true, + "provider" : "JFrog", + "edited" : "2023-02-15T12:36:00Z", + "extended_information" : { + "short_description" : "An inefficient regular expression in setuptools PackageIndex leads to denial of service when processing an attacker-controlled Python package.", + "full_description" : "[Setuptools](https://github.com/pypa/setuptools/) is a package for managing Python packages and dependencies, including tools for building and distributing packages, as well as for discovering and managing 3rd party packages available on PyPI.\n\nThe `find_external_links` function in the `setuptools.package_index` module is used to find external links that are associated with a package available on the Python Package Index (PyPI). These external links are typically URLs to the package's homepage, documentation, source code repository, and other resources that are related to the package.\n\nThe most common scenario of triggering this issue is when running pip with the `--editable` flag. This will cause the vulnerable `find_external_links` function to be invoked\n\nExample of manual usage of `find_external_links` -\n```\nfrom setuptools.package_index import PackageIndex\nimport requests\n\n# Specify the URL of the package page on PyPI\nurl = 'https://pypi.org/project/requests'\n\n# Use the requests library to retrieve the HTML content of the package page\nresponse = requests.get(url)\n\n# Create an instance of the PackageIndex class\nindex = PackageIndex()\n\n# Use the find_external_links function to find external links for the package\nlinks = index.find_external_links(url, response.text)\n\n# Print the name and URL of each external link\nfor link_name, link_url in links:\n print(f'{link_name}: {link_url}')\n```\nThis example will find and print all external links for a package.\n\nTo find a specific url, i.e. the tag containing the `rel=` property, the `find_external_links` function uses a Regular Expression (regex). The regex is too not efficient which can lead to ReDoS on specific user inputs.\nAs a result, a user fetching malicious HTML from a package in PyPI or a custom PackageIndex page may be attacked.", + "jfrog_research_severity" : "Low", + "jfrog_research_severity_reasons" : [ { + "name" : "The issue cannot result in a severe impact (such as remote code execution)", + "description" : "In cases where an attacker can already modify (or fully control) a package, then the attacker can change the package's code and immediately achieve arbitrary code execution. Since exploitation of this vulnerability has virtually the same prerequisites and only achieves DoS, this issue has a very low security impact", + "is_positive" : true + }, { + "name" : "Exploiting the issue requires the user to interact with the vulnerable software", + "description" : "In most cases, exploiting the vulnerability will require user interaction (ex. running pip)", + "is_positive" : true + }, { + "name" : "The prerequisites for exploiting the issue are extremely unlikely", + "description" : "The attackers must be able to change package's content to add its malicious crafted `rel` property.\nMoreover, any reviewer of the package's code will probably detect this and it will probably be caught by the lint and failed the usual lint process.\nAlso, the system installing the Python packages must use the `--editable` pip flag to trigger this vulnerability.", + "is_positive" : true + }, { + "name" : "The issue is trivial to exploit and does not require a published writeup or PoC", + "description" : "Exploiting this vulnerability is easy, as it only depends on the number of spaces between the `rel` property and the `=` symbol and between the `=` symbol to the property's value." + } ] + }, + "cves" : [ { + "cve" : "CVE-2022-40897", + "cwe" : [ "CWE-1333" ], + "cvss_v3_score" : "5.9", + "cvss_v3_vector" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H" + } ], + "components" : { + "pypi://setuptools:57.4.0" : { + "package_name" : "setuptools", + "package_version" : "57.4.0", + "package_type" : "pypi", + "fixed_versions" : [ "[65.5.1]" ], + "infected_versions" : [ "(,65.5.1)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://setuptools:57.4.0" + } ] ] + } + }, + "references" : [ "https://security.netapp.com/advisory/ntap-20240621-0006", "https://pyup.io/vulnerabilities/CVE-2022-40897/52495", "https://setuptools.pypa.io/en/latest", "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/ADES3NLOE5QJKBLGNZNI2RGVOSQXA37R", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ADES3NLOE5QJKBLGNZNI2RGVOSQXA37R", "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/YNA2BAH2ACBZ4TVJZKFLCR7L23BG5C3H", "https://security.netapp.com/advisory/ntap-20240621-0006/", "https://github.com/pypa/setuptools/issues/3659", "https://pyup.io/posts/pyup-discovers-redos-vulnerabilities-in-top-python-packages/", "https://github.com/pypa/setuptools/blob/fe8a98e696241487ba6ac9f91faa38ade939ec5d/setuptools/package_index.py#L200", "https://pyup.io/vulnerabilities/CVE-2022-40897/52495/", "https://security.netapp.com/advisory/ntap-20230214-0001/", "https://nvd.nist.gov/vuln/detail/CVE-2022-40897", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/YNA2BAH2ACBZ4TVJZKFLCR7L23BG5C3H", "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/ADES3NLOE5QJKBLGNZNI2RGVOSQXA37R/", "https://github.com/advisories/GHSA-r9hx-vwmv-q579", "https://github.com/pypa/setuptools/compare/v65.5.0...v65.5.1", "https://security.netapp.com/advisory/ntap-20230214-0001", "https://pyup.io/posts/pyup-discovers-redos-vulnerabilities-in-top-python-packages", "https://github.com/pypa/setuptools/commit/43a9c9bfa6aa626ec2a22540bea28d2ca77964be", "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/YNA2BAH2ACBZ4TVJZKFLCR7L23BG5C3H/" ] + }, { + "severity" : "High", + "summary" : "A vulnerability in the package_index module of pypa/setuptools versions up to 69.1.1 allows for remote code execution via its download functions. These functions, which are used to download packages from URLs provided by users or retrieved from package index servers, are susceptible to code injection. If these functions are exposed to user-controlled inputs, such as package URLs, they can execute arbitrary commands on the system. The issue is fixed in version 70.0.", + "issue_id" : "XRAY-611082", + "is_high_profile" : true, + "provider" : "JFrog", + "edited" : "0001-01-01T00:00:00Z", + "extended_information" : { + "short_description" : "Insufficient input validation in Python's Setuptools may lead to remote code execution by providing a crafted package URL.", + "full_description" : "[Setuptools](https://github.com/pypa/setuptools) is a library that lets developers easily build and distribute Python packages.\n\nA vulnerability has been discovered in the function `_download_vcs()` in Setuptools. This function is responsible for downloading packages from a VCS (a Version Control System - `git` or `hg`). It is used, for example, by `PackageIndex.download()`. It has been found that `_download_vcs()` is prone to command injection, because it executes the VCS commands using `os.system()` like this:\n\n`os.system(f\"{vcs} clone --quiet {url} {filename}\")`\n\nThe `url` variable is provided as an input to the function, and defines the URL from which the package should be downloaded. Thus, an attacker who controls this URL can escape the `{vcs}` command and inject his own shell commands.\n\nThis vulnerability is only applicable if an attacker controls a URL that is provided as a package to download. This URL can be provided in several ways: through `setup.py`, through the CLI (`python setup.py easy_install `), through Python functions such as `PackageIndex.download()`, or through a custom package index server (which is configured in `setup.cfg`).", + "jfrog_research_severity" : "Medium", + "jfrog_research_severity_reasons" : [ { + "name" : "The prerequisites for exploiting the issue are extremely unlikely", + "description" : "The attacker must be able to control a URL that is provided as a package to download or convince the victim to download the malicious package.\nAnother scenario would be an attacker that compromises a package index server, in which case the a package content can already be modified to execute code without needing this vulnerability.", + "is_positive" : true + }, { + "name" : "The issue results in a severe impact (such as remote code execution)", + "description" : "The impact of this vulnerability is RCE and full system compromise." + }, { + "name" : "Exploiting the issue requires the user to interact with the vulnerable software", + "description" : "In most cases, exploiting the vulnerability will require user interaction (ex. running pip).", + "is_positive" : true + } ] + }, + "cves" : [ { + "cve" : "CVE-2024-6345", + "cwe" : [ "CWE-94" ], + "cvss_v3_score" : "8.8", + "cvss_v3_vector" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H" + } ], + "components" : { + "pypi://setuptools:57.4.0" : { + "package_name" : "setuptools", + "package_version" : "57.4.0", + "package_type" : "pypi", + "fixed_versions" : [ "[70.0.0]" ], + "infected_versions" : [ "(,70.0.0)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://setuptools:57.4.0" + } ] ] + } + }, + "references" : [ "https://github.com/advisories/GHSA-cx63-2mw6-8hw5", "https://github.com/pypa/setuptools/pull/4332", "https://huntr.com/bounties/d6362117-ad57-4e83-951f-b8141c6e7ca5", "https://nvd.nist.gov/vuln/detail/CVE-2024-6345", "https://github.com/pypa/setuptools/commit/88807c7062788254f654ea8c03427adc859321f0" ] + }, { + "severity" : "High", + "summary" : "Werkzeug is a comprehensive WSGI web application library. If an upload of a file that starts with CR or LF and then is followed by megabytes of data without these characters: all of these bytes are appended chunk by chunk into internal bytearray and lookup for boundary is performed on growing buffer. This allows an attacker to cause a denial of service by sending crafted multipart data to an endpoint that will parse it. The amount of CPU time required can block worker processes from handling legitimate requests. This vulnerability has been patched in version 3.0.1.", + "issue_id" : "XRAY-534473", + "is_high_profile" : true, + "provider" : "JFrog", + "edited" : "0001-01-01T00:00:00Z", + "extended_information" : { + "short_description" : "A design problem in Werkzeug may lead to denial of service when parsing multipart form data.", + "jfrog_research_severity" : "High", + "jfrog_research_severity_reasons" : [ { + "name" : "The issue is trivial to exploit and does not require a published writeup or PoC", + "description" : "Even though a public exploit is not available, a simple big file (e.g. 50MB in size) that contains CR/LF bytes at the start and then is padded with zeroes would trigger the issue.\nThis file pattern is commonly observed in malware samples and memory dumps." + }, { + "name" : "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", + "description" : "The vulnerability may lead to denial of service when the attacker is flooding the server with malicious file uploads.", + "is_positive" : true + }, { + "name" : "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", + "description" : "An attacker must have access to a multipart form receiving files propagating into the `formparser.parse_form_data()` function.", + "is_positive" : true + }, { + "name" : "The issue results in a severe impact (such as remote code execution)", + "description" : "Denial of Service." + }, { + "name" : "The issue can be exploited by attackers over the network" + } ] + }, + "cves" : [ { + "cve" : "CVE-2023-46136", + "cwe" : [ "CWE-787", "CWE-400" ], + "cvss_v3_score" : "7.5", + "cvss_v3_vector" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } ], + "components" : { + "pypi://werkzeug:1.0.1" : { + "package_name" : "werkzeug", + "package_version" : "1.0.1", + "package_type" : "pypi", + "fixed_versions" : [ "[2.3.8]", "[3.0.1]" ], + "infected_versions" : [ "(,2.3.8)", "[3.0.0,3.0.1)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://werkzeug:1.0.1" + } ] ] + } + }, + "references" : [ "https://github.com/advisories/GHSA-hrfv-mqp8-q5rw", "https://github.com/pallets/werkzeug/commit/f2300208d5e2a5076cbbb4c2aad71096fd040ef9", "https://nvd.nist.gov/vuln/detail/CVE-2023-46136", "https://github.com/pallets/werkzeug/security/advisories/GHSA-hrfv-mqp8-q5rw", "https://github.com/pypa/advisory-database/tree/main/vulns/werkzeug/PYSEC-2023-221.yaml", "https://github.com/pallets/werkzeug/commit/f3c803b3ade485a45f12b6d6617595350c0f03e2", "https://security.netapp.com/advisory/ntap-20231124-0008/", "https://github.com/pallets/werkzeug/commit/b1916c0c083e0be1c9d887ee2f3d696922bfc5c1" ] + }, { + "severity" : "High", + "summary" : "Werkzeug is a comprehensive WSGI web application library. Prior to version 2.2.3, Werkzeug's multipart form data parser will parse an unlimited number of parts, including file parts. Parts can be a small amount of bytes, but each requires CPU time to parse and may use more memory as Python data. If a request can be made to an endpoint that accesses `request.data`, `request.form`, `request.files`, or `request.get_data(parse_form_data=False)`, it can cause unexpectedly high resource usage. This allows an attacker to cause a denial of service by sending crafted multipart data to an endpoint that will parse it. The amount of CPU time required can block worker processes from handling legitimate requests. The amount of RAM required can trigger an out of memory kill of the process. Unlimited file parts can use up memory and file handles. If many concurrent requests are sent continuously, this can exhaust or kill all available workers. Version 2.2.3 contains a patch for this issue.", + "issue_id" : "XRAY-418118", + "provider" : "JFrog", + "edited" : "0001-01-01T00:00:00Z", + "extended_information" : { }, + "cves" : [ { + "cve" : "CVE-2023-25577", + "cwe" : [ "CWE-770", "CWE-400" ], + "cvss_v3_score" : "7.5", + "cvss_v3_vector" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } ], + "components" : { + "pypi://werkzeug:1.0.1" : { + "package_name" : "werkzeug", + "package_version" : "1.0.1", + "package_type" : "pypi", + "fixed_versions" : [ "[2.2.3]" ], + "infected_versions" : [ "(,2.2.3)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://werkzeug:1.0.1" + } ] ] + } + }, + "references" : [ "https://github.com/pallets/werkzeug/commit/517cac5a804e8c4dc4ed038bb20dacd038e7a9f1", "https://github.com/pallets/werkzeug/security/advisories/GHSA-xg9f-g7g7-2323", "https://security.netapp.com/advisory/ntap-20230818-0003", "https://security.netapp.com/advisory/ntap-20230818-0003/", "https://nvd.nist.gov/vuln/detail/CVE-2023-25577", "https://github.com/pallets/werkzeug/releases/tag/2.2.3", "https://github.com/pallets/werkzeug", "https://www.debian.org/security/2023/dsa-5470" ] + }, { + "severity" : "High", + "summary" : "Werkzeug is a comprehensive WSGI web application library. The debugger in affected versions of Werkzeug can allow an attacker to execute code on a developer's machine under some circumstances. This requires the attacker to get the developer to interact with a domain and subdomain they control, and enter the debugger PIN, but if they are successful it allows access to the debugger even if it is only running on localhost. This also requires the attacker to guess a URL in the developer's application that will trigger the debugger. This vulnerability is fixed in 3.0.3.", + "issue_id" : "XRAY-600537", + "is_high_profile" : true, + "provider" : "JFrog", + "edited" : "0001-01-01T00:00:00Z", + "extended_information" : { + "short_description" : "A design problem in Werkzeug may lead to remote code execution when interacting with an attacker-controlled domain.", + "jfrog_research_severity" : "Low", + "jfrog_research_severity_reasons" : [ { + "name" : "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", + "description" : "An attacker must have access to the debugger PIN code.", + "is_positive" : true + }, { + "name" : "Exploiting the issue requires the user to interact with the vulnerable software", + "description" : "This vulnerability requires the attacker to get the developer to interact with a domain and subdomain they control, and enter the debugger PIN.", + "is_positive" : true + }, { + "name" : "The prerequisites for exploiting the issue are extremely unlikely", + "description" : "The prerequisites for exploiting the issue are unlikely because it involves a user interacting with a domain and subdomain that has access to the Debugger PIN. Additionally, the attacker must find a way of triggering the debugger in the application.", + "is_positive" : true + } ], + "remediation" : "##### Development mitigations\n\n**Do not use debugger in production**:\n\n```python\napp.run(debug=False)\n```" + }, + "cves" : [ { + "cve" : "CVE-2024-34069", + "cwe" : [ "CWE-352" ], + "cvss_v3_score" : "7.5", + "cvss_v3_vector" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H" + } ], + "components" : { + "pypi://werkzeug:1.0.1" : { + "package_name" : "werkzeug", + "package_version" : "1.0.1", + "package_type" : "pypi", + "fixed_versions" : [ "[3.0.3]" ], + "infected_versions" : [ "(,3.0.3)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://werkzeug:1.0.1" + } ] ] + } + }, + "references" : [ "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/H4SH32AM3CTPMAAEOIDAN7VU565LO4IR", "https://nvd.nist.gov/vuln/detail/CVE-2024-34069", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HFERFN7PINV4MOGMGA3DPIXJPDCYOEJZ", "https://security.netapp.com/advisory/ntap-20240614-0004/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HFERFN7PINV4MOGMGA3DPIXJPDCYOEJZ/", "https://github.com/pallets/werkzeug/security/advisories/GHSA-2g68-c3qc-8985", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/H4SH32AM3CTPMAAEOIDAN7VU565LO4IR/", "https://security.netapp.com/advisory/ntap-20240614-0004", "https://github.com/pallets/werkzeug/commit/3386395b24c7371db11a5b8eaac0c91da5362692", "https://github.com/advisories/GHSA-2g68-c3qc-8985" ] + }, { + "severity" : "Critical", + "summary" : "Improper parsing of HTTP requests in Pallets Werkzeug v2.1.0 and below allows attackers to perform HTTP Request Smuggling using a crafted HTTP request with multiple requests included inside the body. NOTE: the vendor's position is that this behavior can only occur in unsupported configurations involving development mode and an HTTP server from outside the Werkzeug project", + "issue_id" : "XRAY-211172", + "provider" : "JFrog", + "edited" : "0001-01-01T00:00:00Z", + "extended_information" : { }, + "cves" : [ { + "cve" : "CVE-2022-29361", + "cwe" : [ "CWE-444" ], + "cvss_v2_score" : "7.5", + "cvss_v2_vector" : "CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P", + "cvss_v3_score" : "9.8", + "cvss_v3_vector" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + } ], + "components" : { + "pypi://werkzeug:1.0.1" : { + "package_name" : "werkzeug", + "package_version" : "1.0.1", + "package_type" : "pypi", + "fixed_versions" : [ "[2.1.1]" ], + "infected_versions" : [ "(,2.1.1)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://werkzeug:1.0.1" + } ] ] + } + }, + "references" : [ "https://github.com/pallets/werkzeug/commit/9a3a981d70d2e9ec3344b5192f86fcaf3210cd85", "https://nvd.nist.gov/vuln/detail/CVE-2022-29361", "https://github.com/pallets/werkzeug/issues/2420" ] + }, { + "severity" : "Low", + "summary" : "Werkzeug is a comprehensive WSGI web application library. Browsers may allow \"nameless\" cookies that look like `=value` instead of `key=value`. A vulnerable browser may allow a compromised application on an adjacent subdomain to exploit this to set a cookie like `=__Host-test=bad` for another subdomain. Werkzeug prior to 2.2.3 will parse the cookie `=__Host-test=bad` as __Host-test=bad`. If a Werkzeug application is running next to a vulnerable or malicious subdomain which sets such a cookie using a vulnerable browser, the Werkzeug application will see the bad cookie value but the valid cookie key. The issue is fixed in Werkzeug 2.2.3.", + "issue_id" : "XRAY-418119", + "provider" : "JFrog", + "edited" : "0001-01-01T00:00:00Z", + "extended_information" : { }, + "cves" : [ { + "cve" : "CVE-2023-23934", + "cwe" : [ "CWE-20" ], + "cvss_v3_score" : "3.5", + "cvss_v3_vector" : "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N" + } ], + "components" : { + "pypi://werkzeug:1.0.1" : { + "package_name" : "werkzeug", + "package_version" : "1.0.1", + "package_type" : "pypi", + "fixed_versions" : [ "[2.2.3]" ], + "infected_versions" : [ "(,2.2.3)" ], + "impact_paths" : [ [ { + "component_id" : "root" + }, { + "component_id" : "pypi://werkzeug:1.0.1" + } ] ] + } + }, + "references" : [ "https://security.netapp.com/advisory/ntap-20230818-0003", "https://github.com/pallets/werkzeug/releases/tag/2.2.3", "https://github.com/pallets/werkzeug", "https://github.com/pallets/werkzeug/commit/cf275f42acad1b5950c50ffe8ef58fe62cdce028", "https://security.netapp.com/advisory/ntap-20230818-0003/", "https://nvd.nist.gov/vuln/detail/CVE-2023-23934", "https://www.debian.org/security/2023/dsa-5470", "https://github.com/pallets/werkzeug/security/advisories/GHSA-px8h-6qxv-m22q" ] + } ] +} \ No newline at end of file diff --git a/utils/test_mocks.go b/utils/test_mocks.go index 469b6c19..9fbeaa25 100644 --- a/utils/test_mocks.go +++ b/utils/test_mocks.go @@ -9,6 +9,7 @@ import ( "net/http" "net/http/httptest" "os" + "strings" "testing" ) @@ -93,7 +94,7 @@ func XrayServer(t *testing.T, xrayVersion string) (*httptest.Server, *config.Ser } } } - if r.RequestURI == "/xray/api/v1/scan/graph" { + if strings.HasPrefix(r.RequestURI, "/xray/api/v1/scan/graph") { //"/xray/api/v1/scan/graph" if r.Method == http.MethodPost { w.WriteHeader(http.StatusCreated) _, err := w.Write([]byte(fmt.Sprintf(`{"scan_id" : "%s"}`, TestScaScanId))) @@ -101,6 +102,17 @@ func XrayServer(t *testing.T, xrayVersion string) (*httptest.Server, *config.Ser return } } + if r.Method == http.MethodGet { + w.WriteHeader(http.StatusOK) + content, err := os.ReadFile("../../tests/testdata/other/graphScanResults/graphScanResult.txt") + if !assert.NoError(t, err) { + return + } + _, err = w.Write(content) + if !assert.NoError(t, err) { + return + } + } } }) return serverMock, serverDetails diff --git a/utils/utils.go b/utils/utils.go index c1655b91..6e388f71 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -130,7 +130,7 @@ func splitEnvVar(envVar string) (key, value string) { // This functionallity is being used for Jas scanners and SCA scanner func DumpScanResultsToFileIfNeeded(results interface{}, scanResultsOutputDir string, scanType string) (err error) { // TODO this function should be in utils/results/results.go after the refactor, since it is a common code for Jas and SCA scanners - // TODO AFTER merging the refactor - make sure to create a new directory for every Scan Target and put all its results in this dir, for every Target + // TODO AFTER merging the refactor - make sure to create a new directory for every Scan Target and convert results to Sarif before writing them to file if scanResultsOutputDir == "" || reflect.ValueOf(results).IsNil() { return } From eece33e8bda724e3657c585b5039747bb5e6052e Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 18:43:14 +0300 Subject: [PATCH 09/13] . --- commands/audit/audit_test.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/commands/audit/audit_test.go b/commands/audit/audit_test.go index 6ab50e96..b992fd7a 100644 --- a/commands/audit/audit_test.go +++ b/commands/audit/audit_test.go @@ -120,14 +120,6 @@ func TestAuditWithConfigProfile(t *testing.T) { }) auditParams.SetIsRecursiveScan(true) - /* - baseWd, err := os.Getwd() - assert.NoError(t, err) - chdirCallback := clientTests.ChangeDirWithCallback(t, baseWd, tempDirPath) - defer chdirCallback() - - */ - auditResults, err := RunAudit(auditParams) assert.NoError(t, err) From fa6142aad6aa4cb1003b939f486cb903818f7623 Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 18:46:33 +0300 Subject: [PATCH 10/13] . --- utils/test_mocks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/test_mocks.go b/utils/test_mocks.go index 9fbeaa25..0588b284 100644 --- a/utils/test_mocks.go +++ b/utils/test_mocks.go @@ -94,7 +94,7 @@ func XrayServer(t *testing.T, xrayVersion string) (*httptest.Server, *config.Ser } } } - if strings.HasPrefix(r.RequestURI, "/xray/api/v1/scan/graph") { //"/xray/api/v1/scan/graph" + if strings.HasPrefix(r.RequestURI, "/xray/api/v1/scan/graph") { if r.Method == http.MethodPost { w.WriteHeader(http.StatusCreated) _, err := w.Write([]byte(fmt.Sprintf(`{"scan_id" : "%s"}`, TestScaScanId))) From bb8b65c2c44c54397d61f5c7d1af18ef7c92f191 Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Mon, 9 Sep 2024 18:47:59 +0300 Subject: [PATCH 11/13] . --- commands/audit/audit_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commands/audit/audit_test.go b/commands/audit/audit_test.go index b992fd7a..64cd107e 100644 --- a/commands/audit/audit_test.go +++ b/commands/audit/audit_test.go @@ -179,6 +179,7 @@ func TestAuditWithScansOutputDir(t *testing.T) { assert.NoError(t, err) filesList, err := fileutils.ListFiles(outputDirPath, false) + assert.NoError(t, err) assert.Len(t, filesList, 5) var fileNamesWithoutSuffix []string From 265ef38ca344540d20a7dc341ff397ddb1928b2b Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Thu, 12 Sep 2024 11:09:31 +0300 Subject: [PATCH 12/13] fixed CR comments --- cli/docs/flags.go | 2 +- commands/audit/audit_test.go | 1 - commands/audit/scarunner.go | 14 +++++++++++++- jas/runner/jasrunner.go | 22 ++++++++++++++++++---- utils/utils.go | 20 ++++---------------- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/cli/docs/flags.go b/cli/docs/flags.go index a307f504..e1caf48b 100644 --- a/cli/docs/flags.go +++ b/cli/docs/flags.go @@ -229,7 +229,7 @@ var flagsMap = map[string]components.Flag{ components.WithBoolDefaultValue(true), ), WorkingDirs: components.NewStringFlag(WorkingDirs, "A comma-separated list of relative working directories, to determine audit targets locations."), - OutputDir: components.NewStringFlag(OutputDir, "Some description", components.SetHiddenStrFlag()), + OutputDir: components.NewStringFlag(OutputDir, "Target directory to save partial results to.", components.SetHiddenStrFlag()), ExclusionsAudit: components.NewStringFlag( Exclusions, "List of exclusions separated by semicolons, utilized to skip sub-projects from undergoing an audit. These exclusions may incorporate the * and ? wildcards.", diff --git a/commands/audit/audit_test.go b/commands/audit/audit_test.go index 64cd107e..0ef8f40e 100644 --- a/commands/audit/audit_test.go +++ b/commands/audit/audit_test.go @@ -147,7 +147,6 @@ func TestAuditWithConfigProfile(t *testing.T) { // This test tests audit flow when providing --output-dir flag func TestAuditWithScansOutputDir(t *testing.T) { - // TODO this test needs to be improved to verify the files content after we use the Sarif convertor and write Sarif content into the files mockServer, serverDetails := utils.XrayServer(t, utils.EntitlementsMinVersion) defer mockServer.Close() diff --git a/commands/audit/scarunner.go b/commands/audit/scarunner.go index 7671de02..f8c11046 100644 --- a/commands/audit/scarunner.go +++ b/commands/audit/scarunner.go @@ -152,7 +152,7 @@ func executeScaScanTask(auditParallelRunner *utils.SecurityParallelRunner, serve auditParallelRunner.ResultsMu.Lock() addThirdPartyDependenciesToParams(auditParams, scan.Technology, treeResult.FlatTree, treeResult.FullDepTrees) scan.XrayResults = append(scan.XrayResults, scanResults...) - err = utils.DumpScanResultsToFileIfNeeded(scanResults, auditParams.scanResultsOutputDir, "Sca") + err = dumpScanResponseToFileIfNeeded(scanResults, auditParams.scanResultsOutputDir, "Sca") auditParallelRunner.ResultsMu.Unlock() return } @@ -384,3 +384,15 @@ func buildDependencyTree(scan *utils.ScaScanResult, params *AuditParams) (*Depen } return &treeResult, nil } + +// If an output dir was provided through --output-dir flag, we create in the provided path new file containing the scan results +func dumpScanResponseToFileIfNeeded(results []services.ScanResponse, scanResultsOutputDir string, scanType string) (err error) { + if scanResultsOutputDir == "" || results == nil { + return + } + fileContent, err := json.Marshal(results) + if err != nil { + return fmt.Errorf("failed to write %s scan results to file: %s", scanType, err.Error()) + } + return utils.DumpContentToFile(fileContent, scanResultsOutputDir, scanType) +} diff --git a/jas/runner/jasrunner.go b/jas/runner/jasrunner.go index d6f7b5d6..3d6e8091 100644 --- a/jas/runner/jasrunner.go +++ b/jas/runner/jasrunner.go @@ -1,6 +1,7 @@ package runner import ( + "encoding/json" "fmt" "github.com/jfrog/gofrog/parallel" jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" @@ -15,6 +16,7 @@ import ( clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xsc/services" + "github.com/owenrumney/go-sarif/v2/sarif" "golang.org/x/exp/slices" ) @@ -112,7 +114,7 @@ func runSecretsScan(securityParallelRunner *utils.SecurityParallelRunner, scanne } securityParallelRunner.ResultsMu.Lock() extendedScanResults.SecretsScanResults = append(extendedScanResults.SecretsScanResults, results...) - err = utils.DumpScanResultsToFileIfNeeded(results, scansOutputDir, jasutils.Secrets.String()) + err = dumpSarifRunToFileIfNeeded(results, scansOutputDir, jasutils.Secrets) securityParallelRunner.ResultsMu.Unlock() return } @@ -130,7 +132,7 @@ func runIacScan(securityParallelRunner *utils.SecurityParallelRunner, scanner *j } securityParallelRunner.ResultsMu.Lock() extendedScanResults.IacScanResults = append(extendedScanResults.IacScanResults, results...) - err = utils.DumpScanResultsToFileIfNeeded(results, scansOutputDir, jasutils.IaC.String()) + err = dumpSarifRunToFileIfNeeded(results, scansOutputDir, jasutils.IaC) securityParallelRunner.ResultsMu.Unlock() return } @@ -148,7 +150,7 @@ func runSastScan(securityParallelRunner *utils.SecurityParallelRunner, scanner * } securityParallelRunner.ResultsMu.Lock() extendedScanResults.SastScanResults = append(extendedScanResults.SastScanResults, results...) - err = utils.DumpScanResultsToFileIfNeeded(results, scansOutputDir, jasutils.Sast.String()) + err = dumpSarifRunToFileIfNeeded(results, scansOutputDir, jasutils.Sast) securityParallelRunner.ResultsMu.Unlock() return } @@ -168,8 +170,20 @@ func runContextualScan(securityParallelRunner *utils.SecurityParallelRunner, sca } securityParallelRunner.ResultsMu.Lock() scanResults.ExtendedScanResults.ApplicabilityScanResults = append(scanResults.ExtendedScanResults.ApplicabilityScanResults, results...) - err = utils.DumpScanResultsToFileIfNeeded(results, scansOutputDir, jasutils.Applicability.String()) + err = dumpSarifRunToFileIfNeeded(results, scansOutputDir, jasutils.Applicability) securityParallelRunner.ResultsMu.Unlock() return } } + +// If an output dir was provided through --output-dir flag, we create in the provided path new file containing the scan results +func dumpSarifRunToFileIfNeeded(results []*sarif.Run, scanResultsOutputDir string, scanType jasutils.JasScanType) (err error) { + if scanResultsOutputDir == "" || results == nil { + return + } + fileContent, err := json.Marshal(results) + if err != nil { + return fmt.Errorf("failed to write %s scan results to file: %s", scanType, err.Error()) + } + return utils.DumpContentToFile(fileContent, scanResultsOutputDir, scanType.String()) +} diff --git a/utils/utils.go b/utils/utils.go index 6e388f71..2a6faf0e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -3,12 +3,10 @@ package utils import ( "crypto" "encoding/hex" - "encoding/json" "fmt" "github.com/jfrog/jfrog-client-go/utils/log" "os" "path/filepath" - "reflect" "strings" "time" ) @@ -126,28 +124,18 @@ func splitEnvVar(envVar string) (key, value string) { return split[0], strings.Join(split[1:], "=") } -// If an output dir was provided through --output-dir flag, we create in the provided path, a new directory with a new file containing the scan results -// This functionallity is being used for Jas scanners and SCA scanner -func DumpScanResultsToFileIfNeeded(results interface{}, scanResultsOutputDir string, scanType string) (err error) { +func DumpContentToFile(fileContent []byte, scanResultsOutputDir string, scanType string) (err error) { // TODO this function should be in utils/results/results.go after the refactor, since it is a common code for Jas and SCA scanners // TODO AFTER merging the refactor - make sure to create a new directory for every Scan Target and convert results to Sarif before writing them to file - if scanResultsOutputDir == "" || reflect.ValueOf(results).IsNil() { - return - } - log.Debug(fmt.Sprintf("Scans output directory was provided, saving %s scan results to file...", scanType)) - - fileContent, err := json.Marshal(results) - if err != nil { - return fmt.Errorf("failed to write %s scan results to file: %s", scanType, err.Error()) - } - var curTimeHash string if curTimeHash, err = Md5Hash(time.Now().String()); err != nil { return fmt.Errorf("failed to write %s scan results to file: %s", scanType, err.Error()) } resultsFileName := strings.ToLower(scanType) + "_results_" + curTimeHash + ".json" - if err = os.WriteFile(filepath.Join(scanResultsOutputDir, resultsFileName), fileContent, 0644); err != nil { + resultsFileFullPath := filepath.Join(scanResultsOutputDir, resultsFileName) + log.Debug(fmt.Sprintf("Scans output directory was provided, saving %s scan results to file '%s'...", scanType, resultsFileFullPath)) + if err = os.WriteFile(resultsFileFullPath, fileContent, 0644); err != nil { return fmt.Errorf("failed to write %s scan results to file: %s", scanType, err.Error()) } return From 7951b1ac13705c9f151a12a09614d7bbbaabf4b1 Mon Sep 17 00:00:00 2001 From: Eran Turgeman Date: Sun, 15 Sep 2024 11:23:48 +0300 Subject: [PATCH 13/13] fix CR comment --- commands/audit/scarunner.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/commands/audit/scarunner.go b/commands/audit/scarunner.go index f8c11046..36c58864 100644 --- a/commands/audit/scarunner.go +++ b/commands/audit/scarunner.go @@ -152,7 +152,7 @@ func executeScaScanTask(auditParallelRunner *utils.SecurityParallelRunner, serve auditParallelRunner.ResultsMu.Lock() addThirdPartyDependenciesToParams(auditParams, scan.Technology, treeResult.FlatTree, treeResult.FullDepTrees) scan.XrayResults = append(scan.XrayResults, scanResults...) - err = dumpScanResponseToFileIfNeeded(scanResults, auditParams.scanResultsOutputDir, "Sca") + err = dumpScanResponseToFileIfNeeded(scanResults, auditParams.scanResultsOutputDir, utils.ScaScan) auditParallelRunner.ResultsMu.Unlock() return } @@ -386,7 +386,7 @@ func buildDependencyTree(scan *utils.ScaScanResult, params *AuditParams) (*Depen } // If an output dir was provided through --output-dir flag, we create in the provided path new file containing the scan results -func dumpScanResponseToFileIfNeeded(results []services.ScanResponse, scanResultsOutputDir string, scanType string) (err error) { +func dumpScanResponseToFileIfNeeded(results []services.ScanResponse, scanResultsOutputDir string, scanType utils.SubScanType) (err error) { if scanResultsOutputDir == "" || results == nil { return } @@ -394,5 +394,5 @@ func dumpScanResponseToFileIfNeeded(results []services.ScanResponse, scanResults if err != nil { return fmt.Errorf("failed to write %s scan results to file: %s", scanType, err.Error()) } - return utils.DumpContentToFile(fileContent, scanResultsOutputDir, scanType) + return utils.DumpContentToFile(fileContent, scanResultsOutputDir, scanType.String()) }