From ec2fad9ea555adc0afc445b3daec705b0dcb3b4c Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Fri, 20 Sep 2024 21:24:04 -0400 Subject: [PATCH 1/5] pip: add known good list --- rules/admin/pip_install.yara | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rules/admin/pip_install.yara b/rules/admin/pip_install.yara index 9c6b735b..88586ab6 100644 --- a/rules/admin/pip_install.yara +++ b/rules/admin/pip_install.yara @@ -1,5 +1,14 @@ import "math" +rule pip_installer_known_good : low { + meta: + description = "Installs software using pip from python" + strings: + $distro = /pip3{0,1} install distro/ fullword + condition: + any of them +} + rule pip_installer : high { meta: description = "Installs software using pip from python" @@ -9,7 +18,7 @@ rule pip_installer : high { strings: $ref = /pip[3 \'\"]{0,5}install[ \'\"\w\-\_%]{0,32}/ condition: - $ref + $ref and not pip_installer_known_good } rule pip_installer_fernet : critical { @@ -28,6 +37,9 @@ rule pip_installer_url : critical { ref = "https://checkmarx.com/blog/over-170k-users-affected-by-attack-using-fake-python-infrastructure/" strings: $ref = /pip.{1,5}install.{1,4}https{0,1}:\/\/.{0,64}/ + condition: filesize < 8192 and $ref } + + From afbea5c0726fcefadeaf3dafa7a7778f67bbf4d5 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Sun, 6 Oct 2024 21:57:13 -0400 Subject: [PATCH 2/5] scan: wolfictl inspired output presentation --- cmd/mal/mal.go | 2 +- pkg/action/scan.go | 1 + pkg/malcontent/malcontent.go | 1 + pkg/render/json.go | 4 ++++ pkg/render/markdown.go | 4 ++++ pkg/render/simple.go | 3 +++ pkg/render/terminal.go | 28 ++++++++++++++++++---------- pkg/render/terminal_brief.go | 23 +++++------------------ pkg/render/yaml.go | 4 ++++ 9 files changed, 41 insertions(+), 29 deletions(-) diff --git a/cmd/mal/mal.go b/cmd/mal/mal.go index 35c364aa..c6790ac6 100644 --- a/cmd/mal/mal.go +++ b/cmd/mal/mal.go @@ -499,7 +499,7 @@ func main() { } if res.Files.Len() > 0 { - fmt.Fprintf(os.Stderr, "\ntip: For detailed analysis, run: mal analyze \n") + fmt.Fprintf(os.Stderr, "\nšŸ’” For detailed analysis, try \"mal analyze \"\n") } return nil diff --git a/pkg/action/scan.go b/pkg/action/scan.go index 73fc3e1d..d9b979d1 100644 --- a/pkg/action/scan.go +++ b/pkg/action/scan.go @@ -196,6 +196,7 @@ func recursiveScan(ctx context.Context, c malcontent.Config) (*malcontent.Report var scanPathFindings sync.Map for _, scanPath := range c.ScanPaths { + c.Renderer.Scanning(ctx, scanPath) logger.Debug("recursive scan", slog.Any("scanPath", scanPath)) imageURI := "" ociExtractPath := "" diff --git a/pkg/malcontent/malcontent.go b/pkg/malcontent/malcontent.go index d858f29c..890f5470 100644 --- a/pkg/malcontent/malcontent.go +++ b/pkg/malcontent/malcontent.go @@ -13,6 +13,7 @@ import ( // Renderer is a common interface for Renderers. type Renderer interface { + Scanning(context.Context, string) File(context.Context, *FileReport) error Full(context.Context, *Report) error } diff --git a/pkg/render/json.go b/pkg/render/json.go index 398f99de..7a241eca 100644 --- a/pkg/render/json.go +++ b/pkg/render/json.go @@ -20,6 +20,10 @@ func NewJSON(w io.Writer) JSON { return JSON{w: w} } +func (r JSON) Scanning(_ context.Context, _ string) { + return +} + func (r JSON) File(_ context.Context, _ *malcontent.FileReport) error { return nil } diff --git a/pkg/render/markdown.go b/pkg/render/markdown.go index 89908fdb..07e2cad9 100644 --- a/pkg/render/markdown.go +++ b/pkg/render/markdown.go @@ -41,6 +41,10 @@ func matchFragmentLink(s string) string { return fmt.Sprintf("[%s](https://github.com/search?q=%s&type=code)", s, url.QueryEscape(s)) } +func (r Markdown) Scanning(_ context.Context, _ string) { + return +} + func (r Markdown) File(ctx context.Context, fr *malcontent.FileReport) error { if len(fr.Behaviors) > 0 { markdownTable(ctx, fr, r.w, tableConfig{Title: fmt.Sprintf("## %s [%s]", fr.Path, mdRisk(fr.RiskScore, fr.RiskLevel))}) diff --git a/pkg/render/simple.go b/pkg/render/simple.go index 5bb1beaf..ccaaa9b8 100644 --- a/pkg/render/simple.go +++ b/pkg/render/simple.go @@ -20,6 +20,9 @@ func NewSimple(w io.Writer) Simple { return Simple{w: w} } +func (r Simple) Scanning(_ context.Context, _ string) { +} + func (r Simple) File(_ context.Context, fr *malcontent.FileReport) error { if fr.Skipped != "" { return nil diff --git a/pkg/render/terminal.go b/pkg/render/terminal.go index f860115b..65e2e18a 100644 --- a/pkg/render/terminal.go +++ b/pkg/render/terminal.go @@ -57,25 +57,29 @@ func NewTerminal(w io.Writer) Terminal { } func decorativeRisk(score int, level string) string { - return fmt.Sprintf("%s %s", riskEmoji(score), riskColor(level)) + return fmt.Sprintf("%s %s", riskEmoji(score), riskColor(level, level)) } func darkBrackets(s string) string { return fmt.Sprintf("%s%s%s", color.HiBlackString("["), s, color.HiBlackString("]")) } -func riskColor(level string) string { +func riskInColor(level string) string { + return riskColor(level, level) +} + +func riskColor(level string, text string) string { switch level { case "LOW": - return color.HiGreenString(level) + return color.HiGreenString(text) case "MEDIUM", "MED": - return color.HiYellowString(level) + return color.HiYellowString(text) case "HIGH": - return color.HiRedString(level) + return color.HiRedString(text) case "CRITICAL", "CRIT": - return color.HiMagentaString(level) + return color.HiMagentaString(text) default: - return color.WhiteString(level) + return color.WhiteString(text) } } @@ -90,6 +94,10 @@ func ShortRisk(s string) string { return short } +func (r Terminal) Scanning(_ context.Context, path string) { + fmt.Fprintf(r.w, "šŸ”Ž Scanning %q\n", path) +} + func (r Terminal) File(ctx context.Context, fr *malcontent.FileReport) error { if len(fr.Behaviors) > 0 { renderTable(ctx, fr, r.w, @@ -275,12 +283,12 @@ func renderTable(ctx context.Context, fr *malcontent.FileReport, w io.Writer, rc } evidence := strings.Join(abbreviatedEv, "\n") - risk := riskColor(ShortRisk(k.Behavior.RiskLevel)) + risk := riskInColor(ShortRisk(k.Behavior.RiskLevel)) if k.Behavior.DiffAdded || rc.DiffAdded { if rc.SkipAdded { continue } - risk = fmt.Sprintf("%s%s", color.HiWhiteString("+"), riskColor(ShortRisk(k.Behavior.RiskLevel))) + risk = fmt.Sprintf("%s%s", color.HiWhiteString("+"), riskInColor(ShortRisk(k.Behavior.RiskLevel))) } wKey := wrapKey(k.Key, keyWidth) @@ -290,7 +298,7 @@ func renderTable(ctx context.Context, fr *malcontent.FileReport, w io.Writer, rc if rc.SkipRemoved { continue } - risk = fmt.Sprintf("%s%s", color.WhiteString("-"), riskColor(ShortRisk(k.Behavior.RiskLevel))) + risk = fmt.Sprintf("%s%s", color.WhiteString("-"), riskInColor(ShortRisk(k.Behavior.RiskLevel))) evidence = darkenText(evidence) } diff --git a/pkg/render/terminal_brief.go b/pkg/render/terminal_brief.go index fce17f2b..78d662ea 100644 --- a/pkg/render/terminal_brief.go +++ b/pkg/render/terminal_brief.go @@ -15,7 +15,6 @@ import ( "context" "fmt" "io" - "strings" "github.com/chainguard-dev/malcontent/pkg/malcontent" "github.com/fatih/color" @@ -29,19 +28,8 @@ func NewTerminalBrief(w io.Writer) TerminalBrief { return TerminalBrief{w: w} } -func briefRiskColor(level string) string { - switch level { - case "LOW": - return color.HiGreenString("LOW ") - case "MEDIUM", "MED": - return color.HiYellowString("MED ") - case "HIGH": - return color.HiRedString("HIGH") - case "CRITICAL", "CRIT": - return color.HiMagentaString("CRIT") - default: - return color.WhiteString(level) - } +func (r TerminalBrief) Scanning(_ context.Context, path string) { + fmt.Fprintf(r.w, "šŸ”Ž Scanning %q\n", path) } func (r TerminalBrief) File(_ context.Context, fr *malcontent.FileReport) error { @@ -49,13 +37,12 @@ func (r TerminalBrief) File(_ context.Context, fr *malcontent.FileReport) error return nil } - reasons := []string{} + fmt.Fprintf(r.w, "ā”œā”€ā”€ šŸ“„ %s %s%s%s\n", fr.Path, color.HiBlackString("["), riskInColor(fr.RiskLevel), color.HiBlackString("]")) + for _, b := range fr.Behaviors { - reasons = append(reasons, fmt.Sprintf("%s %s%s%s\n", color.HiYellowString(b.ID), color.HiBlackString("("), b.Description, color.HiBlackString(")"))) + fmt.Fprintf(r.w, "ā”‚ %s %s: %s\n", riskEmoji(fr.RiskScore), riskColor(fr.RiskLevel, b.ID), b.Description) } - fmt.Fprintf(r.w, "%s%s%s %s: \n%s%s\n", color.HiBlackString("["), briefRiskColor(fr.RiskLevel), color.HiBlackString("]"), color.HiGreenString(fr.Path), - color.HiBlackString("- "), strings.Join(reasons, color.HiBlackString("- "))) return nil } diff --git a/pkg/render/yaml.go b/pkg/render/yaml.go index 0d188c3b..591f7dca 100644 --- a/pkg/render/yaml.go +++ b/pkg/render/yaml.go @@ -20,6 +20,10 @@ func NewYAML(w io.Writer) YAML { return YAML{w: w} } +func (r YAML) Scanning(_ context.Context, _ string) { + return +} + func (r YAML) File(_ context.Context, _ *malcontent.FileReport) error { return nil } From 269135d99bfb7f35a85eb80fe3e621b4d2624058 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Sun, 6 Oct 2024 21:59:50 -0400 Subject: [PATCH 3/5] shorten empty Scanning commands --- pkg/render/json.go | 4 +--- pkg/render/markdown.go | 4 +--- pkg/render/simple.go | 3 +-- pkg/render/yaml.go | 4 +--- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/pkg/render/json.go b/pkg/render/json.go index 7a241eca..f37fbb79 100644 --- a/pkg/render/json.go +++ b/pkg/render/json.go @@ -20,9 +20,7 @@ func NewJSON(w io.Writer) JSON { return JSON{w: w} } -func (r JSON) Scanning(_ context.Context, _ string) { - return -} +func (r JSON) Scanning(_ context.Context, _ string) {} func (r JSON) File(_ context.Context, _ *malcontent.FileReport) error { return nil diff --git a/pkg/render/markdown.go b/pkg/render/markdown.go index 07e2cad9..83af2985 100644 --- a/pkg/render/markdown.go +++ b/pkg/render/markdown.go @@ -41,9 +41,7 @@ func matchFragmentLink(s string) string { return fmt.Sprintf("[%s](https://github.com/search?q=%s&type=code)", s, url.QueryEscape(s)) } -func (r Markdown) Scanning(_ context.Context, _ string) { - return -} +func (r Markdown) Scanning(_ context.Context, _ string) {} func (r Markdown) File(ctx context.Context, fr *malcontent.FileReport) error { if len(fr.Behaviors) > 0 { diff --git a/pkg/render/simple.go b/pkg/render/simple.go index ccaaa9b8..a3ccf631 100644 --- a/pkg/render/simple.go +++ b/pkg/render/simple.go @@ -20,8 +20,7 @@ func NewSimple(w io.Writer) Simple { return Simple{w: w} } -func (r Simple) Scanning(_ context.Context, _ string) { -} +func (r Simple) Scanning(_ context.Context, _ string) {} func (r Simple) File(_ context.Context, fr *malcontent.FileReport) error { if fr.Skipped != "" { diff --git a/pkg/render/yaml.go b/pkg/render/yaml.go index 591f7dca..a85afa01 100644 --- a/pkg/render/yaml.go +++ b/pkg/render/yaml.go @@ -20,9 +20,7 @@ func NewYAML(w io.Writer) YAML { return YAML{w: w} } -func (r YAML) Scanning(_ context.Context, _ string) { - return -} +func (r YAML) Scanning(_ context.Context, _ string) {} func (r YAML) File(_ context.Context, _ *malcontent.FileReport) error { return nil From bfcd872d17ad11877235f3c28ee43ac59617e71e Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Sun, 6 Oct 2024 22:09:41 -0400 Subject: [PATCH 4/5] fix merge conflict --- pkg/render/strings.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pkg/render/strings.go b/pkg/render/strings.go index 5a9865e2..09a2cb62 100644 --- a/pkg/render/strings.go +++ b/pkg/render/strings.go @@ -43,6 +43,21 @@ var riskLevels = map[int]string{ 4: "CRITICAL", // critical: certainly malware } +func briefRiskColor(level string) string { + switch level { + case "LOW": + return color.HiGreenString("LOW") + case "MEDIUM", "MED": + return color.HiYellowString("MED") + case "HIGH": + return color.HiRedString("HIGH") + case "CRITICAL", "CRIT": + return color.HiMagentaString("CRIT") + default: + return color.WhiteString(level) + } +} + type StringMatches struct { w io.Writer } @@ -58,6 +73,10 @@ type Match struct { Strings []string } +func (r StringMatches) Scanning(_ context.Context, path string) { + fmt.Fprintf(r.w, "šŸ”Ž Scanning %q\n", path) +} + func (r StringMatches) File(_ context.Context, fr *malcontent.FileReport) error { if len(fr.Behaviors) == 0 { return nil From 28b680051ae08a6da083c84baaf33c4d9293caeb Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Sun, 6 Oct 2024 22:34:39 -0400 Subject: [PATCH 5/5] main: add error wrapping --- cmd/mal/mal.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/mal/mal.go b/cmd/mal/mal.go index 136222d3..7aa4acd2 100644 --- a/cmd/mal/mal.go +++ b/cmd/mal/mal.go @@ -384,7 +384,7 @@ func main() { processPaths, err := action.GetAllProcessPaths(ctx) if err != nil { returnCode = ExitActionFailed - return err + return fmt.Errorf("process paths: %w", err) } for _, p := range processPaths { mc.ScanPaths = append(mc.ScanPaths, p.Path) @@ -394,7 +394,7 @@ func main() { res, err = action.Scan(ctx, mc) if err != nil { returnCode = ExitActionFailed - return err + return fmt.Errorf("scan: %w", err) } err = renderer.Full(ctx, res) @@ -461,7 +461,7 @@ func main() { processPaths, err := action.GetAllProcessPaths(ctx) if err != nil { returnCode = ExitActionFailed - return err + return fmt.Errorf("process paths: %w", err) } for _, p := range processPaths { mc.ScanPaths = append(mc.ScanPaths, p.Path) @@ -471,7 +471,7 @@ func main() { res, err = action.Scan(ctx, mc) if err != nil { returnCode = ExitActionFailed - return err + return fmt.Errorf("scan: %w", err) } err = renderer.Full(ctx, res)