Skip to content

Commit

Permalink
🐛 Fix bug where a '$.name' mapping behaved differently to just 'name'
Browse files Browse the repository at this point in the history
  • Loading branch information
Bradley Kemp committed Feb 3, 2021
1 parent f103bec commit 8ee67d2
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 1 deletion.
18 changes: 17 additions & 1 deletion evaluator/evaluate_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,22 @@ func (rule *RuleEvaluator) matcherMatchesValues(matcher sigma.FieldMatcher, comp
var firstJSONPathField = regexp.MustCompile(`^\$(?:[.]|\[")([a-zA-Z0-9_\-]+)(?:"])?`)

func evaluateJSONPath(expr string, event Event) interface{} {
// First, just try to evaluate the JSONPath expression directly
value, err := jsonpath.Get(expr, event)
if err == nil {
// Got no error so return the value directly
return value
}
if !strings.HasPrefix(err.Error(), "unsupported value type") {
return nil
}

// Got an error: "unsupported value type X for select, expected map[string]interface{} or []interface{}"
// This means we tried to access a nested field that hasn't yet been unmarshalled.
// We try to fix this by finding the top-level field being selected and attempting to unmarshal it.
// This is best effort and only works for top-level fields.
// A longer term solution would be to either build this into the JSONPath library directly or remove this feature and let the user do it.

jsonPathField := firstJSONPathField.FindStringSubmatch(expr)
if jsonPathField == nil {
panic("couldn't parse JSONPath expression")
Expand All @@ -182,7 +198,7 @@ func evaluateJSONPath(expr string, event Event) interface{} {
}
}

value, _ := jsonpath.Get(expr, map[string]interface{}{
value, _ = jsonpath.Get(expr, map[string]interface{}{
jsonPathField[1]: subValue,
})
return value
Expand Down
33 changes: 33 additions & 0 deletions evaluator/fieldmappings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,36 @@ func TestRuleEvaluator_HandlesJSONPathByteSlice(t *testing.T) {
t.Error("If a JSONPath expression encounters a string, the string should be parsed and then matched")
}
}

func TestRuleEvaluator_HandlesToplevelJSONPath(t *testing.T) {
rule := ForRule(sigma.Rule{
Logsource: sigma.Logsource{
Category: "category",
Product: "product",
Service: "service",
},
Detection: sigma.Detection{
Searches: map[string]sigma.Search{
"test": {
FieldMatchers: []sigma.FieldMatcher{{
Field: "name",
Values: []string{"value"},
}},
},
},
Conditions: []sigma.Condition{
{Search: sigma.SearchIdentifier{Name: "test"}}},
},
}, WithConfig(sigma.Config{
FieldMappings: map[string]sigma.FieldMapping{
"name": {TargetNames: []string{"$.toplevel"}},
},
}))

result, _ := rule.Matches(context.Background(), map[string]interface{}{
"toplevel": "value",
})
if !result.Match {
t.Error("A single-level JSONPath expression (e.g. Name: $.name) should be behave identically to a plain field mapping (e.g. Name: name)")
}
}

0 comments on commit 8ee67d2

Please sign in to comment.