Add enrichment and SID coverage gates
This commit is contained in:
parent
93ac47dd82
commit
66472134d8
32 changed files with 1623 additions and 31 deletions
|
|
@ -8,6 +8,7 @@ never auto-deletion. ``is_owned`` is injectable for testing.
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import json
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
|
@ -28,7 +29,7 @@ _ALWAYS_REVIEW = {"reverse_shell", "egress"}
|
|||
class Verdict:
|
||||
label: str # LIKELY_FP or REVIEW
|
||||
reason: str
|
||||
suggest: str = "" # optional config line to suppress future copies
|
||||
suggest: str = "" # optional TOML snippet to suppress future copies
|
||||
|
||||
|
||||
def _exe_for(alert: dict, processes: list[dict]) -> str:
|
||||
|
|
@ -39,6 +40,32 @@ def _exe_for(alert: dict, processes: list[dict]) -> str:
|
|||
return ""
|
||||
|
||||
|
||||
def _comm_from_detail(detail: str) -> str:
|
||||
m = re.search(r"\bcomm=([^\s]+)", detail)
|
||||
return m.group(1) if m else ""
|
||||
|
||||
|
||||
def _alert_context(alert: dict) -> str:
|
||||
sid = alert.get("sid") or "unknown"
|
||||
classtype = alert.get("classtype") or "unknown"
|
||||
return f"sid={sid} classtype={classtype}"
|
||||
|
||||
|
||||
def _toml_string_array(name: str, values) -> str:
|
||||
items = ", ".join(json.dumps(v) for v in sorted(set(values)) if v)
|
||||
return f"{name} = [{items}]"
|
||||
|
||||
|
||||
def _allow_comm_suggestion(alert: dict, cfg: Config, key: str, comm: str,
|
||||
why: str) -> str:
|
||||
current = getattr(cfg, key)
|
||||
return "\n".join((
|
||||
f"# Triage suggestion for {_alert_context(alert)}.",
|
||||
f"# Add only after verifying this {comm!r} activity is expected: {why}.",
|
||||
_toml_string_array(key, set(current) | {comm}),
|
||||
))
|
||||
|
||||
|
||||
def triage_alert(alert: dict, processes: list[dict], cfg: Config,
|
||||
is_owned: Callable[[str], bool] = is_package_owned) -> Verdict:
|
||||
sig = alert.get("signature", "")
|
||||
|
|
@ -73,11 +100,15 @@ def triage_alert(alert: dict, processes: list[dict], cfg: Config,
|
|||
if comm in cfg.listener_allow_comms:
|
||||
return Verdict(LIKELY_FP, f"comm '{comm}' is allow-listed")
|
||||
if exe and is_owned(exe):
|
||||
owner = comm
|
||||
return Verdict(LIKELY_FP,
|
||||
f"listener binary is package-owned ({comm})",
|
||||
suggest=f'listener_allow_comms += "{comm}"')
|
||||
return Verdict(REVIEW, f"unrecognized listener {addr} ({comm})")
|
||||
suggest=_allow_comm_suggestion(
|
||||
alert, cfg, "listener_allow_comms", comm,
|
||||
"package-owned listener"))
|
||||
return Verdict(REVIEW, f"unrecognized listener {addr} ({comm})",
|
||||
suggest=_allow_comm_suggestion(
|
||||
alert, cfg, "listener_allow_comms", comm,
|
||||
"known service listener"))
|
||||
|
||||
if sig == "new_suid":
|
||||
path = detail.rsplit(": ", 1)[-1]
|
||||
|
|
@ -85,6 +116,42 @@ def triage_alert(alert: dict, processes: list[dict], cfg: Config,
|
|||
return Verdict(LIKELY_FP, "SUID binary is package-owned")
|
||||
return Verdict(REVIEW, f"unowned SUID binary: {path}")
|
||||
|
||||
if sig == "input_snooper":
|
||||
comm = _comm_from_detail(detail)
|
||||
if comm:
|
||||
return Verdict(REVIEW, "input device access — verify this is local input stack",
|
||||
suggest=_allow_comm_suggestion(
|
||||
alert, cfg, "input_snooper_allow_comms", comm,
|
||||
"expected input-device reader"))
|
||||
return Verdict(REVIEW, "input device access — verify this is local input stack")
|
||||
|
||||
if sig == "credential_access":
|
||||
comm = _comm_from_detail(detail)
|
||||
if comm:
|
||||
return Verdict(REVIEW, "credential file access — verify the process role",
|
||||
suggest=_allow_comm_suggestion(
|
||||
alert, cfg, "credential_access_allow_comms", comm,
|
||||
"expected credential-store reader"))
|
||||
return Verdict(REVIEW, "credential file access — verify the process role")
|
||||
|
||||
if sig == "stealth_network":
|
||||
comm = _comm_from_detail(detail)
|
||||
if comm and comm != "?":
|
||||
return Verdict(REVIEW, "unusual socket family — verify the protocol use",
|
||||
suggest=_allow_comm_suggestion(
|
||||
alert, cfg, "stealth_network_allow_comms", comm,
|
||||
"expected raw/special-protocol socket owner"))
|
||||
return Verdict(REVIEW, "unusual socket family — verify the protocol use")
|
||||
|
||||
if sig == "memory_obfuscation":
|
||||
comm = _comm_from_detail(detail)
|
||||
if comm:
|
||||
return Verdict(REVIEW, "executable or RWX memory mapping — verify runtime/JIT use",
|
||||
suggest=_allow_comm_suggestion(
|
||||
alert, cfg, "memory_obfuscation_allow_comms", comm,
|
||||
"expected executable memory runtime"))
|
||||
return Verdict(REVIEW, "executable or RWX memory mapping — verify runtime/JIT use")
|
||||
|
||||
if sig == "persistence":
|
||||
return Verdict(REVIEW, "a persistence-relevant file changed — confirm intent")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue