forked from bradleyjkemp/sigma-go
/
modifiers.go
169 lines (154 loc) · 5.49 KB
/
modifiers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package evaluator
import (
"encoding/base64"
"fmt"
"gopkg.in/yaml.v3"
"net"
"reflect"
"regexp"
"strings"
)
type valueComparator func(actual interface{}, expected interface{}) (bool, error)
func baseComparator(actual interface{}, expected interface{}) (bool, error) {
switch {
case actual == nil && expected == "null":
// special case: "null" should match the case where a field isn't present (and so actual is nil)
return true, nil
default:
// The Sigma spec defines that by default comparisons are case-insensitive
return strings.EqualFold(coerceString(actual), coerceString(expected)), nil
}
}
type valueModifier func(next valueComparator) valueComparator
var modifiers = map[string]valueModifier{
"contains": func(_ valueComparator) valueComparator {
return func(actual interface{}, expected interface{}) (bool, error) {
// The Sigma spec defines that by default comparisons are case-insensitive
return strings.Contains(strings.ToLower(coerceString(actual)), strings.ToLower(coerceString(expected))), nil
}
},
"endswith": func(_ valueComparator) valueComparator {
return func(actual interface{}, expected interface{}) (bool, error) {
// The Sigma spec defines that by default comparisons are case-insensitive
return strings.HasSuffix(strings.ToLower(coerceString(actual)), strings.ToLower(coerceString(expected))), nil
}
},
"startswith": func(_ valueComparator) valueComparator {
return func(actual interface{}, expected interface{}) (bool, error) {
return strings.HasPrefix(strings.ToLower(coerceString(actual)), strings.ToLower(coerceString(expected))), nil
}
},
"base64": func(next valueComparator) valueComparator {
return func(actual interface{}, expected interface{}) (bool, error) {
return next(actual, base64.StdEncoding.EncodeToString([]byte(coerceString(expected))))
}
},
"re": func(_ valueComparator) valueComparator {
return func(actual interface{}, expected interface{}) (bool, error) {
re, err := regexp.Compile(coerceString(expected))
if err != nil {
return false, err
}
return re.MatchString(coerceString(actual)), nil
}
},
"cidr": func(_ valueComparator) valueComparator {
return func(actual interface{}, expected interface{}) (bool, error) {
_, cidr, err := net.ParseCIDR(coerceString(expected))
if err != nil {
return false, err
}
ip := net.ParseIP(coerceString(actual))
return cidr.Contains(ip), nil
}
},
"gt": func(_ valueComparator) valueComparator {
return func(actual interface{}, expected interface{}) (bool, error) {
gt, _, _, _, err := compareNumeric(actual, expected)
return gt, err
}
},
"gte": func(_ valueComparator) valueComparator {
return func(actual interface{}, expected interface{}) (bool, error) {
_, gte, _, _, err := compareNumeric(actual, expected)
return gte, err
}
},
"lt": func(_ valueComparator) valueComparator {
return func(actual interface{}, expected interface{}) (bool, error) {
_, _, lt, _, err := compareNumeric(actual, expected)
return lt, err
}
},
"lte": func(_ valueComparator) valueComparator {
return func(actual interface{}, expected interface{}) (bool, error) {
_, _, _, lte, err := compareNumeric(actual, expected)
return lte, err
}
},
}
func coerceString(v interface{}) string {
switch vv := v.(type) {
case string:
return vv
case []byte:
return string(vv)
default:
return fmt.Sprint(vv)
}
}
// coerceNumeric makes both operands into the widest possible number of the same type
func coerceNumeric(left, right interface{}) (interface{}, interface{}, error) {
leftV := reflect.ValueOf(left)
leftType := reflect.ValueOf(left).Type()
rightV := reflect.ValueOf(right)
rightType := reflect.ValueOf(right).Type()
switch {
// Both integers or both floats? Return directly
case leftType.Kind() == reflect.Int && rightType.Kind() == reflect.Int:
fallthrough
case leftType.Kind() == reflect.Float64 && rightType.Kind() == reflect.Float64:
return left, right, nil
// Mixed integer, float? Return two floats
case leftType.Kind() == reflect.Int && rightType.Kind() == reflect.Float64:
fallthrough
case leftType.Kind() == reflect.Float64 && rightType.Kind() == reflect.Int:
floatType := reflect.TypeOf(float64(0))
return leftV.Convert(floatType).Interface(), rightV.Convert(floatType).Interface(), nil
// One or more strings? Parse and recurse.
// We use `yaml.Unmarshal` to parse the string because it's a cheat's way of parsing either an integer or a float
case leftType.Kind() == reflect.String:
var leftParsed interface{}
if err := yaml.Unmarshal([]byte(left.(string)), &leftParsed); err != nil {
return nil, nil, err
}
return coerceNumeric(leftParsed, right)
case rightType.Kind() == reflect.String:
var rightParsed interface{}
if err := yaml.Unmarshal([]byte(right.(string)), &rightParsed); err != nil {
return nil, nil, err
}
return coerceNumeric(left, rightParsed)
default:
return nil, nil, fmt.Errorf("cannot coerce %T and %T to numeric", left, right)
}
}
func compareNumeric(left, right interface{}) (gt, gte, lt, lte bool, err error) {
left, right, err = coerceNumeric(left, right)
if err != nil {
return
}
switch left.(type) {
case int:
left := left.(int)
right := right.(int)
return left > right, left >= right, left < right, left <= right, nil
case float64:
left := left.(float64)
right := right.(float64)
return left > right, left >= right, left < right, left <= right, nil
default:
err = fmt.Errorf("internal, please report! coerceNumeric returned unexpected types %T and %T", left, right)
return
}
}