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

Allow for rule severity overrides; add default ignore tags #481

Merged
merged 6 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 13 additions & 1 deletion cmd/mal/mal.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,18 @@ func main() {
minRisk = -1
}

// Add the default tags to ignore regardless of whether they're passed in or not
defaultIgnore := []string{
"false_positive",
"ignore",
}

for _, t := range defaultIgnore {
if !slices.Contains(ignoreTags, t) {
ignoreTags = append(ignoreTags, t)
}
}

if outputFlag != "" {
outFile, err = os.OpenFile(outputFlag, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
if err != nil {
Expand Down Expand Up @@ -266,7 +278,7 @@ func main() {
},
&cli.StringFlag{
Name: "ignore-tags",
Value: "",
Value: "false_positive,ignore",
Usage: "Rule tags to ignore",
Destination: &ignoreTagsFlag,
},
Expand Down
3 changes: 3 additions & 0 deletions pkg/malcontent/malcontent.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ type Behavior struct {

// ID is the original map key from map[string]*Behavior
ID string `json:",omitempty" yaml:",omitempty"`

// Name is the value of m.Rule
RuleName string `json:",omitempty" yaml:",omitempty"`
}

type FileReport struct {
Expand Down
72 changes: 58 additions & 14 deletions pkg/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,24 @@ var thirdPartyCriticalSources = map[string]bool{
"JPCERT": true,
}

// authorWithURLRe matcehs "Arnim Rupp (https://github.com/ruppde)"
// authorWithURLRe matches "Arnim Rupp (https://github.com/ruppde)"
var authorWithURLRe = regexp.MustCompile(`(.*?) \((http.*)\)`)

var threatHuntingKeywordRe = regexp.MustCompile(`Detection patterns for the tool '(.*)' taken from the ThreatHunting-Keywords github project`)

var dateRe = regexp.MustCompile(`[a-z]{3}\d{1,2}`)

var Levels = map[string]int{
"harmless": 0,
"notable": 2,
"medium": 2,
"suspicious": 3,
"weird": 3,
"high": 3,
"crit": 4,
"critical": 4,
}

func thirdPartyKey(path string, rule string) string {
// include the directory
pathParts := strings.Split(path, "/")
Expand Down Expand Up @@ -174,18 +185,8 @@ func behaviorRisk(ns string, rule string, tags []string) int {
risk = 2
}

levels := map[string]int{
"harmless": 0,
"notable": 2,
"medium": 2,
"suspicious": 3,
"weird": 3,
"high": 3,
"crit": 4,
"critical": 4,
}
for _, tag := range tags {
if r, ok := levels[tag]; ok {
if r, ok := Levels[tag]; ok {
risk = r
break
}
Expand Down Expand Up @@ -327,10 +328,10 @@ func Generate(ctx context.Context, path string, mrs yara.MatchRules, c malconten
var syscalls []string

ignoreMalcontent := false
key := ""
overallRiskScore := 0
riskCounts := map[int]int{}
risk := 0
key := ""
riskCounts := map[int]int{}

// If we're running a scan, only diplay findings of the highest risk
// Return an empty file report if the highest risk is medium or lower
Expand All @@ -343,9 +344,18 @@ func Generate(ctx context.Context, path string, mrs yara.MatchRules, c malconten
}

for _, m := range mrs {
override := false
if all(m.Rule == NAME, ignoreSelf) {
ignoreMalcontent = true
}

for _, t := range m.Tags {
if t == "override" {
override = true
break
}
}

risk = behaviorRisk(m.Namespace, m.Rule, m.Tags)
if risk > overallRiskScore {
overallRiskScore = risk
Expand All @@ -369,12 +379,19 @@ func Generate(ctx context.Context, path string, mrs yara.MatchRules, c malconten
MatchStrings: matchStrings(m.Rule, m.Strings),
RiskLevel: RiskLevels[risk],
RiskScore: risk,
RuleName: m.Rule,
RuleURL: ruleURL,
}

k := ""
v := ""

// Store match rules in a map for future override operations
mrsMap := make(map[string]yara.MatchRule)
for _, m := range mrs {
mrsMap[m.Rule] = m
}

for _, meta := range m.Metas {
k = meta.Identifier
v = fmt.Sprintf("%s", meta.Value)
Expand All @@ -383,6 +400,13 @@ func Generate(ctx context.Context, path string, mrs yara.MatchRules, c malconten
continue
}

// If we find a match in the map for the metadata key, that's the rule to override
// Ensure that the override tag is present on the override rule
var overrideRule string
if match, exists := mrsMap[k]; exists && override {
overrideRule = match.Rule
}

switch k {
case "author":
b.RuleAuthor = v
Expand Down Expand Up @@ -425,6 +449,26 @@ func Generate(ctx context.Context, path string, mrs yara.MatchRules, c malconten
syscalls = append(syscalls, sy...)
case "cap":
caps = append(caps, v)
// If we find a rule to override, pull in that rule's configuration and update the severity
case overrideRule:
var overrideSev int
if sev, ok := Levels[v]; ok {
overrideSev = sev
}
// Find the behavior for the overridden (original) rule
// Store its behavior in the current behavior and remove the original behavior
for i, ob := range fr.Behaviors {
if ob.RuleName == overrideRule {
Copy link
Member Author

@egibs egibs Oct 3, 2024

Choose a reason for hiding this comment

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

@tstromberg -- let me know if you think we should pull in any more of the original rule's fields (author, URL, etc.).

Copy link
Collaborator

Choose a reason for hiding this comment

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

My suggestion: preserve everything in the original rule; just override the severity and add a suffix to the description.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated in: bbe0701 (#481)

Example of the description suffix:

MED   secrets/bash_history     access .bash_history file, overridden by            .bash_history                     
                               yes_it_is_really_bash                                            

b = ob
b.RuleName = m.Rule
b.Description = fmt.Sprintf("%s, [%s]", b.Description, m.Rule)
b.RiskScore = overrideSev
b.RiskLevel = RiskLevels[overrideSev]

// Remove the original rule from the behaviors slice
fr.Behaviors = slices.Delete(fr.Behaviors, i, i+1)
}
}
}
}

Expand Down