| @@ -0,0 +1,6 @@ | ||
| + | dist/ | |
| + | __pycache__/ | |
| + | *.pyc | |
| + | .pytest_cache/ | |
| + | .venv/ | |
| + | venv/ |
| @@ -0,0 +1,8 @@ | ||
| + | sigma-cli==3.0.2 | |
| + | pysigma-backend-splunk==2.1.0 | |
| + | pysigma-backend-elasticsearch==2.0.3 | |
| + | pysigma-backend-kusto==1.0.1 | |
| + | pysigma-pipeline-sysmon==2.0.0 | |
| + | pysigma-pipeline-windows==2.0.0 | |
| + | pytest==8.3.3 | |
| + | PyYAML==6.0.3 |
| @@ -0,0 +1,24 @@ | ||
| + | title: SSH Brute Force | |
| + | id: 975ada2e-e9a5-4ac0-b420-f8c020c64a24 | |
| + | status: experimental | |
| + | description: > | |
| + | Correlates repeated SSH authentication failures from a single source within a short | |
| + | window. This is the alerting rule; the per-event base rule (ssh_auth_failure) is | |
| + | informational on its own. | |
| + | references: | |
| + | - https://attack.mitre.org/techniques/T1110/ | |
| + | author: Zion Boggan | |
| + | date: 2026-05-15 | |
| + | tags: | |
| + | - attack.credential_access | |
| + | - attack.t1110 | |
| + | correlation: | |
| + | type: event_count | |
| + | rules: | |
| + | - ssh_auth_failure | |
| + | group-by: | |
| + | - src_ip | |
| + | timespan: 2m | |
| + | condition: | |
| + | gte: 8 | |
| + | level: high |
| @@ -0,0 +1,24 @@ | ||
| + | title: SSH Authentication Failure | |
| + | name: ssh_auth_failure | |
| + | id: cc6fd1c9-b264-4be8-bb53-b6f4e2af9776 | |
| + | status: experimental | |
| + | description: Base detection for a single failed SSH authentication, used by the brute-force correlation. | |
| + | references: | |
| + | - https://attack.mitre.org/techniques/T1110/ | |
| + | author: Zion Boggan | |
| + | date: 2026-05-15 | |
| + | tags: | |
| + | - attack.credential_access | |
| + | - attack.t1110 | |
| + | logsource: | |
| + | product: linux | |
| + | service: sshd | |
| + | detection: | |
| + | selection: | |
| + | - Message|contains: 'Failed password for' | |
| + | - Message|contains: 'Invalid user' | |
| + | - Message|startswith: 'Connection closed by authenticating user' | |
| + | condition: selection | |
| + | falsepositives: | |
| + | - Users fat-fingering passwords; the correlation rule is what should alert. | |
| + | level: informational |
| @@ -0,0 +1,35 @@ | ||
| + | title: Potential Reverse Shell via Shell Interpreter | |
| + | id: 389cb62c-20d8-4367-a9e7-a809e0ba71d0 | |
| + | status: experimental | |
| + | description: > | |
| + | Detects common reverse-shell one-liners spawned through a Unix shell - bash/sh redirecting | |
| + | to a TCP device, or interpreters opening a socket back to an attacker. | |
| + | references: | |
| + | - https://attack.mitre.org/techniques/T1059/004/ | |
| + | - https://gtfobins.github.io/ | |
| + | author: Zion Boggan | |
| + | date: 2026-05-16 | |
| + | tags: | |
| + | - attack.execution | |
| + | - attack.t1059.004 | |
| + | logsource: | |
| + | product: linux | |
| + | category: process_creation | |
| + | detection: | |
| + | selection_bash_tcp: | |
| + | CommandLine|contains: | |
| + | - '/dev/tcp/' | |
| + | - '/dev/udp/' | |
| + | selection_interpreters: | |
| + | CommandLine|contains: | |
| + | - 'socket.socket(' | |
| + | - 'sh -i' | |
| + | - 'bash -i' | |
| + | - 'mkfifo /tmp/' | |
| + | - 'nc -e' | |
| + | - 'ncat -e' | |
| + | - 'socat tcp' | |
| + | condition: selection_bash_tcp or selection_interpreters | |
| + | falsepositives: | |
| + | - Legitimate use of netcat/socat by admins; baseline expected usage. | |
| + | level: high |
| @@ -0,0 +1,35 @@ | ||
| + | title: Systemd Service Created in User-Writable or Temp Path | |
| + | id: d4a5c6f7-0f65-4c8c-a1a7-d4428e73bb05 | |
| + | status: experimental | |
| + | description: > | |
| + | Detects creation of a systemd unit file outside the standard package-managed locations | |
| + | or pointing ExecStart at a temp/home path - a common Linux persistence technique. | |
| + | references: | |
| + | - https://attack.mitre.org/techniques/T1543/002/ | |
| + | author: Zion Boggan | |
| + | date: 2026-05-16 | |
| + | tags: | |
| + | - attack.persistence | |
| + | - attack.t1543.002 | |
| + | logsource: | |
| + | product: linux | |
| + | category: file_event | |
| + | detection: | |
| + | selection_unit: | |
| + | TargetFilename|endswith: '.service' | |
| + | TargetFilename|contains: | |
| + | - '/etc/systemd/system/' | |
| + | - '/.config/systemd/user/' | |
| + | - '/run/systemd/system/' | |
| + | filter_pkg: | |
| + | Image|endswith: | |
| + | - '/dpkg' | |
| + | - '/rpm' | |
| + | - '/apt' | |
| + | - '/yum' | |
| + | - '/dnf' | |
| + | - '/systemctl' | |
| + | condition: selection_unit and not filter_pkg | |
| + | falsepositives: | |
| + | - Admins hand-installing units; baseline known unit deployments. | |
| + | level: medium |
| @@ -0,0 +1,37 @@ | ||
| + | title: Suspicious LSASS Process Access | |
| + | id: dcfda42d-c1a7-4106-aa96-7912201d9221 | |
| + | status: experimental | |
| + | description: > | |
| + | Detects process access to lsass.exe with access rights commonly used to read | |
| + | process memory (credential dumping). Tuned to the granted-access masks seen with | |
| + | Mimikatz, comsvcs MiniDump, and similar tooling rather than the broad 0x1010 alone. | |
| + | references: | |
| + | - https://attack.mitre.org/techniques/T1003/001/ | |
| + | - https://github.com/SwiftOnSecurity/sysmon-config | |
| + | author: Zion Boggan | |
| + | date: 2026-05-12 | |
| + | tags: | |
| + | - attack.credential_access | |
| + | - attack.t1003.001 | |
| + | logsource: | |
| + | product: windows | |
| + | category: process_access | |
| + | detection: | |
| + | selection: | |
| + | TargetImage|endswith: '\lsass.exe' | |
| + | GrantedAccess: | |
| + | - '0x1010' | |
| + | - '0x1410' | |
| + | - '0x143a' | |
| + | - '0x1438' | |
| + | - '0x1fffff' | |
| + | filter_known: | |
| + | SourceImage|endswith: | |
| + | - '\wininit.exe' | |
| + | - '\csrss.exe' | |
| + | - '\MsMpEng.exe' | |
| + | - '\wmiprvse.exe' | |
| + | condition: selection and not filter_known | |
| + | falsepositives: | |
| + | - EDR and AV products legitimately reading LSASS; baseline and add to filter_known. | |
| + | level: high |
| @@ -0,0 +1,34 @@ | ||
| + | title: Suspicious Rundll32 Execution | |
| + | id: c7b35acf-1122-4446-8d2d-95e7883f1064 | |
| + | status: experimental | |
| + | description: > | |
| + | Detects rundll32.exe invoked in patterns associated with proxy execution of malicious | |
| + | code: no DLL argument, javascript: protocol handlers, or execution from user-writable paths. | |
| + | references: | |
| + | - https://attack.mitre.org/techniques/T1218/011/ | |
| + | - https://lolbas-project.github.io/lolbas/Binaries/Rundll32/ | |
| + | author: Zion Boggan | |
| + | date: 2026-05-13 | |
| + | tags: | |
| + | - attack.defense_evasion | |
| + | - attack.t1218.011 | |
| + | logsource: | |
| + | product: windows | |
| + | category: process_creation | |
| + | detection: | |
| + | selection_img: | |
| + | Image|endswith: '\rundll32.exe' | |
| + | selection_patterns: | |
| + | CommandLine|contains: | |
| + | - 'javascript:' | |
| + | - 'mshtml,RunHTMLApplication' | |
| + | - '.dll,#1' | |
| + | - '\AppData\' | |
| + | - '\Temp\' | |
| + | - '\ProgramData\' | |
| + | selection_nodll: | |
| + | CommandLine|re: 'rundll32(\.exe)?["'']?\s*$' | |
| + | condition: selection_img and (selection_patterns or selection_nodll) | |
| + | falsepositives: | |
| + | - Rare legitimate use; baseline expected DLL invocations. | |
| + | level: high |
| @@ -0,0 +1,37 @@ | ||
| + | title: PowerShell EncodedCommand Execution | |
| + | id: 99410337-d137-401f-ac19-3977cfa523a4 | |
| + | status: experimental | |
| + | description: > | |
| + | Detects PowerShell launched with an encoded command payload, frequently used to | |
| + | hide intent during initial access and execution. Pairs an -EncodedCommand style flag | |
| + | with the common evasion switches. | |
| + | references: | |
| + | - https://attack.mitre.org/techniques/T1059/001/ | |
| + | author: Zion Boggan | |
| + | date: 2026-05-12 | |
| + | tags: | |
| + | - attack.execution | |
| + | - attack.t1059.001 | |
| + | logsource: | |
| + | product: windows | |
| + | category: process_creation | |
| + | detection: | |
| + | selection_img: | |
| + | - Image|endswith: '\powershell.exe' | |
| + | - Image|endswith: '\pwsh.exe' | |
| + | - OriginalFileName: 'PowerShell.EXE' | |
| + | selection_enc: | |
| + | CommandLine|contains: | |
| + | - ' -enc ' | |
| + | - ' -encodedcommand ' | |
| + | - ' -ec ' | |
| + | selection_flags: | |
| + | CommandLine|contains: | |
| + | - ' -nop' | |
| + | - ' -noprofile' | |
| + | - ' -w hidden' | |
| + | - ' -windowstyle hidden' | |
| + | condition: selection_img and (selection_enc or selection_flags) | |
| + | falsepositives: | |
| + | - Some management tooling and installers use encoded commands; baseline by parent process. | |
| + | level: medium |
| @@ -0,0 +1,41 @@ | ||
| + | title: Office Application Spawns Scripting or LOLBin Process | |
| + | id: 67a438b8-b930-4760-ab4e-99810ad450a3 | |
| + | status: experimental | |
| + | description: > | |
| + | Detects a Microsoft Office application spawning a script interpreter or living-off-the-land | |
| + | binary, a classic phishing-to-execution pivot (macro or exploit dropping a child process). | |
| + | references: | |
| + | - https://attack.mitre.org/techniques/T1566/ | |
| + | - https://attack.mitre.org/techniques/T1059/ | |
| + | author: Zion Boggan | |
| + | date: 2026-05-13 | |
| + | tags: | |
| + | - attack.initial_access | |
| + | - attack.t1566.001 | |
| + | - attack.execution | |
| + | - attack.t1059.001 | |
| + | logsource: | |
| + | product: windows | |
| + | category: process_creation | |
| + | detection: | |
| + | selection: | |
| + | ParentImage|endswith: | |
| + | - '\winword.exe' | |
| + | - '\excel.exe' | |
| + | - '\powerpnt.exe' | |
| + | - '\outlook.exe' | |
| + | - '\mspub.exe' | |
| + | Image|endswith: | |
| + | - '\cmd.exe' | |
| + | - '\powershell.exe' | |
| + | - '\pwsh.exe' | |
| + | - '\wscript.exe' | |
| + | - '\cscript.exe' | |
| + | - '\mshta.exe' | |
| + | - '\rundll32.exe' | |
| + | - '\regsvr32.exe' | |
| + | - '\bitsadmin.exe' | |
| + | condition: selection | |
| + | falsepositives: | |
| + | - Some enterprise document templates legitimately call scripts; rare, baseline per environment. | |
| + | level: high |
| @@ -0,0 +1,34 @@ | ||
| + | title: Scheduled Task Created via Security Log | |
| + | id: 7f08d779-de77-47cd-9d21-48774fdb1225 | |
| + | status: experimental | |
| + | description: > | |
| + | Detects creation of a scheduled task (Security event 4698). Scheduled tasks are a durable | |
| + | persistence and execution mechanism; tuned to flag tasks running interpreters or binaries | |
| + | from user-writable locations. | |
| + | references: | |
| + | - https://attack.mitre.org/techniques/T1053/005/ | |
| + | author: Zion Boggan | |
| + | date: 2026-05-14 | |
| + | tags: | |
| + | - attack.persistence | |
| + | - attack.t1053.005 | |
| + | - attack.execution | |
| + | logsource: | |
| + | product: windows | |
| + | service: security | |
| + | detection: | |
| + | selection: | |
| + | EventID: 4698 | |
| + | selection_susp: | |
| + | TaskContent|contains: | |
| + | - 'powershell' | |
| + | - 'mshta' | |
| + | - 'rundll32' | |
| + | - 'cmd.exe' | |
| + | - '\AppData\' | |
| + | - '\Temp\' | |
| + | - '\Users\Public\' | |
| + | condition: selection and selection_susp | |
| + | falsepositives: | |
| + | - Legitimate software and admin tasks; baseline scheduled tasks per environment. | |
| + | level: medium |
| @@ -0,0 +1,35 @@ | ||
| + | title: New Windows Service Installed | |
| + | id: adfa52d1-fd8a-4095-ae3f-5708ec0ff71b | |
| + | status: experimental | |
| + | description: > | |
| + | Detects installation of a new Windows service (System event 7045). Service creation is a | |
| + | common persistence and privilege-execution mechanism; tuned to flag services pointing at | |
| + | user-writable paths or script interpreters. | |
| + | references: | |
| + | - https://attack.mitre.org/techniques/T1543/003/ | |
| + | author: Zion Boggan | |
| + | date: 2026-05-14 | |
| + | tags: | |
| + | - attack.persistence | |
| + | - attack.t1543.003 | |
| + | logsource: | |
| + | product: windows | |
| + | service: system | |
| + | detection: | |
| + | selection: | |
| + | EventID: 7045 | |
| + | Provider_Name: 'Service Control Manager' | |
| + | selection_susp: | |
| + | ImagePath|contains: | |
| + | - '\Users\' | |
| + | - '\AppData\' | |
| + | - '\Temp\' | |
| + | - '\ProgramData\' | |
| + | - 'powershell' | |
| + | - 'cmd.exe /c' | |
| + | - 'cmd /c' | |
| + | - 'rundll32' | |
| + | condition: selection and selection_susp | |
| + | falsepositives: | |
| + | - Some software installs services from ProgramData; baseline known-good ImagePaths. | |
| + | level: high |
| @@ -0,0 +1,59 @@ | ||
| + | import re | |
| + | from pathlib import Path | |
| + | ||
| + | import pytest | |
| + | import yaml | |
| + | ||
| + | RULES = sorted((Path(__file__).resolve().parents[1] / "rules").rglob("*.yml")) | |
| + | TACTIC_TAGS = { | |
| + | "attack.reconnaissance", "attack.resource_development", "attack.initial_access", | |
| + | "attack.execution", "attack.persistence", "attack.privilege_escalation", | |
| + | "attack.defense_evasion", "attack.credential_access", "attack.discovery", | |
| + | "attack.lateral_movement", "attack.collection", "attack.command_and_control", | |
| + | "attack.exfiltration", "attack.impact", | |
| + | } | |
| + | TECHNIQUE_RE = re.compile(r"^attack\.t\d{4}(\.\d{3})?$") | |
| + | ||
| + | ||
| + | def load(path): | |
| + | return yaml.safe_load(path.read_text()) | |
| + | ||
| + | ||
| + | def test_rules_exist(): | |
| + | assert RULES, "no rules found" | |
| + | ||
| + | ||
| + | @pytest.mark.parametrize("path", RULES, ids=[p.name for p in RULES]) | |
| + | def test_rule_schema(path): | |
| + | rule = load(path) | |
| + | for field in ("title", "id", "status", "description", "tags", "level"): | |
| + | assert rule.get(field), f"{path.name} missing {field}" | |
| + | assert "detection" in rule or "correlation" in rule, f"{path.name} has no detection/correlation" | |
| + | assert rule["level"] in {"informational", "low", "medium", "high", "critical"} | |
| + | assert rule["status"] in {"experimental", "test", "stable", "deprecated", "unsupported"} | |
| + | ||
| + | ||
| + | @pytest.mark.parametrize("path", RULES, ids=[p.name for p in RULES]) | |
| + | def test_attack_tags(path): | |
| + | tags = load(path).get("tags", []) | |
| + | techniques = [t for t in tags if TECHNIQUE_RE.match(t)] | |
| + | assert techniques, f"{path.name} has no ATT&CK technique tag" | |
| + | for t in tags: | |
| + | if t.startswith("attack."): | |
| + | assert t in TACTIC_TAGS or TECHNIQUE_RE.match(t), f"{path.name} bad tag {t}" | |
| + | ||
| + | ||
| + | def test_unique_ids(): | |
| + | ids = [load(p)["id"] for p in RULES] | |
| + | dupes = {i for i in ids if ids.count(i) > 1} | |
| + | assert not dupes, f"duplicate rule ids: {dupes}" | |
| + | ||
| + | ||
| + | def test_correlation_refs_resolve(): | |
| + | names = {load(p).get("name") for p in RULES if load(p).get("name")} | |
| + | for path in RULES: | |
| + | rule = load(path) | |
| + | corr = rule.get("correlation") | |
| + | if corr: | |
| + | for ref in corr.get("rules", []): | |
| + | assert ref in names, f"{path.name} references unknown rule '{ref}'" |