forked from markuskont/go-sigma-rule-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrule.go
147 lines (132 loc) · 4.03 KB
/
rule.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
package sigma
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v2"
)
// RuleHandle is a meta object containing all fields from raw yaml, but is enhanced to also
// hold debugging info from the tool, such as source file path, etc
type RuleHandle struct {
Rule
Path string `json:"path"`
Multipart bool `json:"multipart"`
NoCollapseWS bool `json:"noCollapseWS"`
}
// Rule defines raw rule conforming to sigma rule specification
// https://github.com/Neo23x0/sigma/wiki/Specification
// only meant to be used for parsing yaml that matches Sigma rule definition
type Rule struct {
Author string `yaml:"author" json:"author"`
Description string `yaml:"description" json:"description"`
Falsepositives []string `yaml:"falsepositives" json:"falsepositives"`
Fields []string `yaml:"fields" json:"fields"`
ID string `yaml:"id" json:"id"`
Level string `yaml:"level" json:"level"`
Title string `yaml:"title" json:"title"`
Status string `yaml:"status" json:"status"`
References []string `yaml:"references" json:"references"`
Logsource `yaml:"logsource" json:"logsource"`
Detection `yaml:"detection" json:"detection"`
Tags `yaml:"tags" json:"tags"`
}
// NewRuleList reads a list of sigma rule paths and parses them to rule objects
func NewRuleList(files []string, skip, noCollapseWS bool) ([]RuleHandle, error) {
if len(files) == 0 {
return nil, fmt.Errorf("missing rule file list")
}
errs := make([]ErrParseYaml, 0)
rules := make([]RuleHandle, 0)
loop:
for i, path := range files {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var r Rule
if err := yaml.Unmarshal(data, &r); err != nil {
if skip {
errs = append(errs, ErrParseYaml{
Path: path,
Count: i,
Err: err,
})
continue loop
}
return nil, &ErrParseYaml{Err: err, Path: path}
}
rules = append(rules, RuleHandle{
Path: path,
Rule: r,
NoCollapseWS: noCollapseWS,
Multipart: func() bool {
return !bytes.HasPrefix(data, []byte("---")) && bytes.Contains(data, []byte("---"))
}(),
})
}
return rules, func() error {
if len(errs) > 0 {
return ErrBulkParseYaml{Errs: errs}
}
return nil
}()
}
// Logsource represents the logsource field in sigma rule
// It defines relevant event streams and is used for pre-filtering
type Logsource struct {
Product string `yaml:"product" json:"product"`
Category string `yaml:"category" json:"category"`
Service string `yaml:"service" json:"service"`
Definition string `yaml:"definition" json:"definition"`
}
// Detection represents the detection field in sigma rule
// contains condition expression and identifier fields for building AST
type Detection map[string]interface{}
func (d Detection) Extract() map[string]interface{} {
tx := make(map[string]interface{})
for k, v := range d {
if k != "condition" {
tx[k] = v
}
}
return tx
}
// Tags contains a metadata list for tying positive matches together with other threat intel sources
// For example, for attaching MITRE ATT&CK tactics or techniques to the event
type Tags []string
// Result is an object returned on positive sigma match
type Result struct {
Tags `json:"tags"`
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
}
// Results should be returned when single event matches multiple rules
type Results []Result
// NewRuleFileList finds all yaml files from defined root directories
// Subtree is scanned recursively
// No file validation, other than suffix matching
func NewRuleFileList(dirs []string) ([]string, error) {
if len(dirs) == 0 {
return nil, errors.New("rule directories undefined")
}
out := make([]string, 0)
for _, dir := range dirs {
if err := filepath.Walk(dir, func(
path string,
info os.FileInfo,
err error,
) error {
if !info.IsDir() && strings.HasSuffix(path, "yml") {
out = append(out, path)
}
return err
}); err != nil {
return out, err
}
}
return out, nil
}