Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Malicious code scanner #296

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions commands/audit/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package audit
import (
"errors"
"fmt"
"github.com/jfrog/jfrog-cli-security/jas/maliciouscode"
"strings"

"github.com/jfrog/gofrog/parallel"
Expand Down Expand Up @@ -195,7 +196,7 @@ func (auditCmd *AuditCommand) Run() (err error) {
func (auditCmd *AuditCommand) getResultWriter(cmdResults *results.SecurityCommandResults) *output.ResultsWriter {
var messages []string
if !cmdResults.EntitledForJas {
messages = []string{coreutils.PrintTitle("The ‘jf audit’ command also supports JFrog Advanced Security features, such as 'Contextual Analysis', 'Secret Detection', 'IaC Scan' and ‘SAST’.\nThis feature isn't enabled on your system. Read more - ") + coreutils.PrintLink(utils.JasInfoURL)}
messages = []string{coreutils.PrintTitle("The ‘jf audit’ command also supports JFrog Advanced Security features, such as 'Contextual Analysis', 'Secret Detection', 'Malicious Code Scan', 'IaC Scan' and ‘SAST’.\nThis feature isn't enabled on your system. Read more - ") + coreutils.PrintLink(utils.JasInfoURL)}
}
return output.NewResultsWriter(cmdResults).
SetOutputFormat(auditCmd.OutputFormat()).
Expand All @@ -204,17 +205,17 @@ func (auditCmd *AuditCommand) getResultWriter(cmdResults *results.SecurityComman
SetSubScansPerformed(auditCmd.ScansToPerform())
}

func ProcessResultsAndOutput(auditResults *results.SecurityCommandResults, outputWriter *output.ResultsWriter, failBuild bool) (err error) {
func ProcessResultsAndOutput(cmdResults *results.SecurityCommandResults, outputWriter *output.ResultsWriter, failBuild bool) (err error) {
if err = outputWriter.PrintScanResults(); err != nil {
// Error printing the results, return the error and the scan results errors.
return errors.Join(err, auditResults.GetErrors())
return errors.Join(err, cmdResults.GetErrors())
}
if err = auditResults.GetErrors(); err != nil {
if err = cmdResults.GetErrors(); err != nil {
// Return the scan results errors.
return
}
// Only in case Xray's context was given (!auditCmd.IncludeVulnerabilities), and the user asked to fail the build accordingly, do so.
if failBuild && !auditResults.ResultContext.IncludeVulnerabilities && results.CheckIfFailBuild(auditResults.GetScaScansXrayResults()) {
if failBuild && !cmdResults.ResultContext.IncludeVulnerabilities && results.CheckIfFailBuild(cmdResults.GetScaScansXrayResults()) {
err = results.NewFailBuildError()
}
return
Expand Down Expand Up @@ -344,6 +345,7 @@ func createJasScansTasks(auditParallelRunner *utils.SecurityParallelRunner, scan
ConfigProfile: auditParams.configProfile,
ScansToPerform: auditParams.ScansToPerform(),
SecretsScanType: secrets.SecretsScannerType,
MaliciousScanType: maliciouscode.MaliciousScannerType,
DirectDependencies: auditParams.DirectDependencies(),
ThirdPartyApplicabilityScan: auditParams.thirdPartyApplicabilityScan,
ApplicableScanType: applicability.ApplicabilityScannerType,
Expand Down
2 changes: 2 additions & 0 deletions commands/scan/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/jfrog/jfrog-cli-security/jas/maliciouscode"
"os/exec"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -495,6 +496,7 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults
Module: module,
ScansToPerform: utils.GetAllSupportedScans(),
SecretsScanType: secrets.SecretsScannerDockerScanType,
MaliciousScanType: maliciouscode.MaliciousScannerDockerScanType,
DirectDependencies: directDepsListFromVulnerabilities(*graphScanResults),
ApplicableScanType: applicability.ApplicabilityDockerScanScanType,
ScanResults: targetResults,
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
)

replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20250126110945-81abbdde452f
replace github.com/jfrog/jfrog-client-go => ../jfrog-client-go
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for remote push, while developing, make sure you are pointing to a valid branch.
This is expceting the code to be in the machine locally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250128111343-44586261061e

Expand All @@ -123,3 +123,5 @@ replace github.com/jfrog/jfrog-cli-artifactory => github.com/jfrog/jfrog-cli-art
// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev

// replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go dev

replace github.com/jfrog/jfrog-apps-config => ../jfrog-apps-config
101 changes: 101 additions & 0 deletions jas/maliciouscode/maliciouscodescanner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package maliciouscode

import (
clientutils "github.com/jfrog/jfrog-client-go/utils"
"path/filepath"

jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go"
"github.com/jfrog/jfrog-cli-security/jas"
"github.com/jfrog/jfrog-cli-security/utils"
"github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils"
"github.com/jfrog/jfrog-cli-security/utils/jasutils"
"github.com/jfrog/jfrog-client-go/utils/log"
"github.com/owenrumney/go-sarif/v2/sarif"
)

const (
maliciousScanCommand = "mal"
maliciousDocsUrlSuffix = "malicious"

MaliciousScannerType MaliciousScanType = "malicious-scan" // #nosec
MaliciousScannerDockerScanType MaliciousScanType = "malicious-docker-scan" // #nosec
)

type MaliciousScanType string

type MaliciousScanManager struct {
scanner *jas.JasScanner
scanType MaliciousScanType
configFileName string
resultsFileName string
}

// The getMaliciousScanResults function runs the malicious code scan flow, which includes the following steps:
// Creating an MaliciousSecretManager object.
// Running the analyzer manager executable.
// Parsing the analyzer manager results.
func RunMaliciousScan(scanner *jas.JasScanner, scanType MaliciousScanType, module jfrogappsconfig.Module, threadId int) (vulnerabilitiesResults []*sarif.Run, violationsResults []*sarif.Run, err error) {
var scannerTempDir string
if scannerTempDir, err = jas.CreateScannerTempDirectory(scanner, jasutils.MaliciousCode.String()); err != nil {
return
}
maliciousScanManager := newMaliciousScanManager(scanner, scanType, scannerTempDir)
log.Info(clientutils.GetLogMsgPrefix(threadId, false) + "Running Malicious code scan...")
if vulnerabilitiesResults, violationsResults, err = maliciousScanManager.scanner.Run(maliciousScanManager, module); err != nil {
return
}
log.Info(utils.GetScanFindingsLog(utils.MaliciousCodeScan, sarifutils.GetResultsLocationCount(vulnerabilitiesResults...), sarifutils.GetResultsLocationCount(violationsResults...), threadId))
return
}

func newMaliciousScanManager(scanner *jas.JasScanner, scanType MaliciousScanType, scannerTempDir string) (manager *MaliciousScanManager) {
return &MaliciousScanManager{
scanner: scanner,
scanType: scanType,
configFileName: filepath.Join(scannerTempDir, "config.yaml"),
resultsFileName: filepath.Join(scannerTempDir, "results.sarif"),
}
}

func (msm *MaliciousScanManager) Run(module jfrogappsconfig.Module) (vulnerabilitiesSarifRuns []*sarif.Run, violationsSarifRuns []*sarif.Run, err error) {
if err = msm.createConfigFile(module, msm.scanner.Exclusions...); err != nil {
return
}
if err = msm.runAnalyzerManager(); err != nil {
return
}
return jas.ReadJasScanRunsFromFile(msm.resultsFileName, module.SourceRoot, maliciousDocsUrlSuffix, msm.scanner.MinSeverity)
}

type maliciousScanConfig struct {
Scans []maliciousScanConfiguration `yaml:"scans"`
}

type maliciousScanConfiguration struct {
Roots []string `yaml:"roots"`
Output string `yaml:"output"`
Type string `yaml:"type"`
SkippedDirs []string `yaml:"skipped-folders"`
}

func (m *MaliciousScanManager) createConfigFile(module jfrogappsconfig.Module, exclusions ...string) error {
roots, err := jas.GetSourceRoots(module, module.Scanners.MaliciousCode)
if err != nil {
return err
}
configFileContent := maliciousScanConfig{
Scans: []maliciousScanConfiguration{
{
Roots: roots,
Output: m.resultsFileName,
Type: string(m.scanType),
SkippedDirs: jas.GetExcludePatterns(module, module.Scanners.MaliciousCode, exclusions...),
},
},
}
return jas.CreateScannersConfigFile(m.configFileName, configFileContent, jasutils.MaliciousCode)
}

func (m *MaliciousScanManager) runAnalyzerManager() error {
return m.scanner.AnalyzerManager.Exec(m.configFileName, maliciousScanCommand, filepath.Dir(m.scanner.AnalyzerManager.AnalyzerManagerFullPath), m.scanner.ServerDetails, m.scanner.EnvVars)
}
1 change: 1 addition & 0 deletions jas/maliciouscode/maliciouscodescanner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package maliciouscode
43 changes: 35 additions & 8 deletions jas/runner/jasrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package runner
import (
"errors"
"fmt"
"github.com/jfrog/jfrog-cli-security/jas/maliciouscode"

"github.com/jfrog/gofrog/parallel"
jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go"
Expand Down Expand Up @@ -33,6 +34,8 @@ type JasRunnerParams struct {

ScansToPerform []utils.SubScanType

// Malicious code scan flags
MaliciousScanType maliciouscode.MaliciousScanType
// Secret scan flags
SecretsScanType secrets.SecretsScanType
// Contextual Analysis scan flags
Expand All @@ -51,27 +54,31 @@ func AddJasScannersTasks(params JasRunnerParams) (generalError error) {
if params.Scanner.AnalyzerManager.AnalyzerManagerFullPath, generalError = jas.GetAnalyzerManagerExecutable(); generalError != nil {
return fmt.Errorf("failed to set analyzer manager executable path: %s", generalError.Error())
}
// For docker scan we support only secrets and contextual scans.
// For docker scan we support only secrets, malicious code, and contextual scans.
runAllScanners := false
if params.ApplicableScanType == applicability.ApplicabilityScannerType || params.SecretsScanType == secrets.SecretsScannerType {
runAllScanners = true
}
if generalError = addJasScanTaskForModuleIfNeeded(params, utils.ContextualAnalysisScan, runContextualScan(params.Runner, params.Scanner, params.ScanResults, params.Module, params.DirectDependencies, params.ThirdPartyApplicabilityScan, params.ApplicableScanType, params.TargetOutputDir)); generalError != nil {
return
}
//if generalError = addJasScanTaskForModuleIfNeeded(params, utils.ContextualAnalysisScan, runContextualScan(params.Runner, params.Scanner, params.ScanResults, params.Module, params.DirectDependencies, params.ThirdPartyApplicabilityScan, params.ApplicableScanType, params.TargetOutputDir)); generalError != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't forget to uncomment when done

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

// return
//}
if params.ThirdPartyApplicabilityScan {
// Don't execute other scanners when scanning third party dependencies.
return
}
if generalError = addJasScanTaskForModuleIfNeeded(params, utils.SecretsScan, runSecretsScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.SecretsScanType, params.TargetOutputDir)); generalError != nil {
//if generalError = addJasScanTaskForModuleIfNeeded(params, utils.SecretsScan, runSecretsScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.SecretsScanType, params.TargetOutputDir)); generalError != nil {
// return
//}
if generalError = addJasScanTaskForModuleIfNeeded(params, utils.MaliciousCodeScan, runMaliciousScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.MaliciousScanType, params.TargetOutputDir)); generalError != nil {
return
}
if !runAllScanners {
return
}
if generalError = addJasScanTaskForModuleIfNeeded(params, utils.IacScan, runIacScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.TargetOutputDir)); generalError != nil {
return
}
//if generalError = addJasScanTaskForModuleIfNeeded(params, utils.IacScan, runIacScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.TargetOutputDir)); generalError != nil {
// return
//}
return
return addJasScanTaskForModuleIfNeeded(params, utils.SastScan, runSastScan(params.Runner, params.Scanner, params.ScanResults.JasResults, params.Module, params.TargetOutputDir, params.SignedDescriptions))
}

Expand Down Expand Up @@ -102,6 +109,8 @@ func addJasScanTaskForModuleIfNeeded(params JasRunnerParams, subScan utils.SubSc
enabled = params.ConfigProfile.Modules[0].ScanConfig.IacScannerConfig.EnableIacScan
case jasutils.Applicability:
enabled = params.ConfigProfile.Modules[0].ScanConfig.EnableContextualAnalysisScan
case jasutils.MaliciousCode:
enabled = params.ConfigProfile.Modules[0].ScanConfig.MaliciousScannerConfig.EnableMaliciousScan
}
if enabled {
generalError = addModuleJasScanTask(jasType, params.Runner, task, params.ScanResults, params.AllowPartialResults)
Expand Down Expand Up @@ -145,6 +154,24 @@ func runSecretsScan(securityParallelRunner *utils.SecurityParallelRunner, scanne
}
}

func runMaliciousScan(securityParallelRunner *utils.SecurityParallelRunner, scanner *jas.JasScanner, extendedScanResults *results.JasScansResults,
module jfrogappsconfig.Module, maliciousScanType maliciouscode.MaliciousScanType, scansOutputDir string) parallel.TaskFunc {
return func(threadId int) (err error) {
defer func() {
securityParallelRunner.JasScannersWg.Done()
}()
vulnerabilitiesResults, violationsResults, err := maliciouscode.RunMaliciousScan(scanner, maliciousScanType, module, threadId)
securityParallelRunner.ResultsMu.Lock()
defer securityParallelRunner.ResultsMu.Unlock()
// We first add the scan results and only then check for errors, so we can store the exit code in order to report it in the end
extendedScanResults.AddJasScanResults(jasutils.MaliciousCode, vulnerabilitiesResults, violationsResults, jas.GetAnalyzerManagerExitCode(err))
if err = jas.ParseAnalyzerManagerError(jasutils.MaliciousCode, err); err != nil {
return fmt.Errorf("%s%s", clientutils.GetLogMsgPrefix(threadId, false), err.Error())
}
return dumpSarifRunToFileIfNeeded(scansOutputDir, jasutils.MaliciousCode, vulnerabilitiesResults, violationsResults)
}
}

func runIacScan(securityParallelRunner *utils.SecurityParallelRunner, scanner *jas.JasScanner, extendedScanResults *results.JasScansResults,
module jfrogappsconfig.Module, scansOutputDir string) parallel.TaskFunc {
return func(threadId int) (err error) {
Expand Down
14 changes: 14 additions & 0 deletions utils/formats/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,20 @@ func ConvertToSecretsTableRow(rows []SourceCodeRow) (tableRows []secretsTableRow
return
}

func ConvertToMaliciousTableRow(rows []SourceCodeRow) (tableRows []maliciousTableRow) {
for i := range rows {
tableRows = append(tableRows, maliciousTableRow{
severity: rows[i].Severity,
file: rows[i].File,
lineColumn: strconv.Itoa(rows[i].StartLine) + ":" + strconv.Itoa(rows[i].StartColumn),
evidence: rows[i].Snippet,
maliciousType: rows[i].Finding,
})

}
return
}

func ConvertToIacOrSastTableRow(rows []SourceCodeRow) (tableRows []iacOrSastTableRow) {
for i := range rows {
tableRows = append(tableRows, iacOrSastTableRow{
Expand Down
2 changes: 2 additions & 0 deletions utils/formats/simplejsonapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type SimpleJsonResults struct {
SecretsViolations []SourceCodeRow `json:"secretsViolations"`
IacsViolations []SourceCodeRow `json:"iacViolations"`
SastViolations []SourceCodeRow `json:"sastViolations"`
MaliciousVulnerabilities []SourceCodeRow `json:"malicious_code"`
MaliciousViolations []SourceCodeRow `json:"maliciousViolations"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
MaliciousVulnerabilities []SourceCodeRow `json:"malicious_code"`
MaliciousViolations []SourceCodeRow `json:"maliciousViolations"`
MaliciousVulnerabilities []SourceCodeRow `json:"maliciousCode"`
MaliciousViolations []SourceCodeRow `json:"maliciousViolations"`

make sure the formats are the same...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Errors []SimpleJsonError `json:"errors"`
Statuses ScanStatus `json:"scansStatus"`
MultiScanId string `json:"multiScanId,omitempty"`
Expand Down
19 changes: 15 additions & 4 deletions utils/formats/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
IacResult SummaryResultType = "IAC"
SecretsResult SummaryResultType = "Secrets"
SastResult SummaryResultType = "SAST"
MaliciousResult SummaryResultType = "MaliciousCode"
ScaResult SummaryResultType = "SCA"
ScaSecurityResult SummaryResultType = "Security"
ScaLicenseResult SummaryResultType = "License"
Expand Down Expand Up @@ -36,10 +37,11 @@ type ScanSummary struct {
}

type ScanResultSummary struct {
ScaResults *ScaScanResultSummary `json:"sca,omitempty"`
IacResults *ResultSummary `json:"iac,omitempty"`
SecretsResults *ResultSummary `json:"secrets,omitempty"`
SastResults *ResultSummary `json:"sast,omitempty"`
ScaResults *ScaScanResultSummary `json:"sca,omitempty"`
IacResults *ResultSummary `json:"iac,omitempty"`
SecretsResults *ResultSummary `json:"secrets,omitempty"`
SastResults *ResultSummary `json:"sast,omitempty"`
MaliciousResults *ResultSummary `json:"malicious_code,omitempty"`
}

type ScanViolationsSummary struct {
Expand Down Expand Up @@ -184,6 +186,9 @@ func (srs *ScanResultSummary) GetTotal(filterTypes ...SummaryResultType) (total
if srs.SastResults != nil && isFilterApply(SastResult, filterTypes) {
total += srs.SastResults.GetTotal()
}
if srs.MaliciousResults != nil && isFilterApply(MaliciousResult, filterTypes) {
total += srs.MaliciousResults.GetTotal()
}
if srs.ScaResults == nil {
return
}
Expand Down Expand Up @@ -229,6 +234,9 @@ func (ss *ScanResultSummary) GetSummaryBySeverity() (summary ResultSummary) {
if ss.SastResults != nil {
summary = MergeResultSummaries(summary, *ss.SastResults)
}
if ss.MaliciousResults != nil {
summary = MergeResultSummaries(summary, *ss.MaliciousResults)
}
return
}

Expand Down Expand Up @@ -306,6 +314,9 @@ func extractIssuesToSummary(issues *ScanResultSummary, destination *ScanResultSu
if issues.SastResults != nil {
destination.SastResults = mergeResultSummariesPointers(destination.SastResults, issues.SastResults)
}
if issues.MaliciousResults != nil {
destination.MaliciousResults = mergeResultSummariesPointers(destination.MaliciousResults, issues.MaliciousResults)
}
}

func mergeResultSummariesPointers(summaries ...*ResultSummary) (merged *ResultSummary) {
Expand Down
11 changes: 11 additions & 0 deletions utils/formats/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type ResultsTables struct {
// Secrets
SecretsVulnerabilitiesTable []secretsTableRow
SecretsViolationsTable []secretsTableRow

MaliciousVulnerabilitiesTable []maliciousTableRow
MaliciousViolationsTable []maliciousTableRow
}

// Used for vulnerabilities and security violations
Expand Down Expand Up @@ -155,6 +158,14 @@ type secretsTableRow struct {
watch string `col-name:"Watch Name" omitempty:"true"`
}

type maliciousTableRow struct {
severity string `col-name:"Severity"`
file string `col-name:"File"`
lineColumn string `col-name:"Line:Column"`
evidence string `col-name:"Evidence"`
maliciousType string `col-name:"Malicious Code Type"`
}

type iacOrSastTableRow struct {
severity string `col-name:"Severity"`
file string `col-name:"File"`
Expand Down
Loading
Loading