feat(ttp): E.3.8 R0001-R0030 command cohort

30 YAMLs for the shell/command rule cohort per Appendix B (rules/ttp/).
Splits into engine-active (R0007-R0029, regex on command_text /
raw_url / user_agent) and lifter-bound (R0001-R0006, R0030 — the
v0 RuleEngine cannot count auth attempts, do identity rollups, or
parse fingerprint blobs; the BehavioralLifter / IdentityLifter /
CredentialLifter consume them by rule_id at E.3.9 / E.3.13).

test_command_rules.py asserts:
- every R000N has a YAML that compiles
- lifter-bound rules NEVER fire from the v0 engine (regression
  guard against a YAML drifting into a regex match.spec)
- engine-active rules meet their Appendix-C precision target
  against the seed corpus (≥0.95 high-conf, ≥0.80 medium)

Conftest fixes: precision_engine moved to module-scope so module-
scope precomputed dispatch fixture (fired_by_label) can request it;
_RULES_DIR path bumped from parents[2] to parents[3] so the loader
resolves the project root regardless of pytest cwd; make_event
synthesizes attacker_uuid so TTPTag's anchor invariant is satisfied.

Seed corpus broadened: positive examples for every regex rule plus
6 negative examples across innocuous shell verbs (ls, echo, cd, ps,
df, free) so FPs surface in precision rather than passing vacuously.
This commit is contained in:
2026-05-01 09:16:38 -04:00
parent c635478442
commit b1fe1f9403
33 changed files with 758 additions and 15 deletions

20
rules/ttp/R0001.yaml Normal file
View File

@@ -0,0 +1,20 @@
rule_id: R0001
rule_version: 1
name: generic_auth_brute
description: |
Repeated failed auth across services/accounts. Cross-event;
emitted by the BehavioralLifter (E.3.9) — v0 RuleEngine cannot
count.
applies_to:
- auth_attempt
match:
kind: lifter:auth_brute_generic
fail_threshold: 5
window_minutes: 5
emits:
- tactic: TA0006
technique_id: T1110
confidence: 0.85
evidence_fields:
- fail_count
- service

20
rules/ttp/R0002.yaml Normal file
View File

@@ -0,0 +1,20 @@
rule_id: R0002
rule_version: 1
name: password_guessing
description: |
Multiple passwords tried against a single account in a window.
Cross-event; BehavioralLifter (E.3.9).
applies_to:
- auth_attempt
match:
kind: lifter:password_guessing
pw_threshold: 5
window_minutes: 5
emits:
- tactic: TA0006
technique_id: T1110
sub_technique_id: T1110.001
confidence: 0.85
evidence_fields:
- username
- password_count

19
rules/ttp/R0003.yaml Normal file
View File

@@ -0,0 +1,19 @@
rule_id: R0003
rule_version: 1
name: password_spraying
description: |
Same password tried across many accounts (identity-rollup).
IdentityLifter (E.3.13).
applies_to:
- identity
match:
kind: lifter:password_spraying
account_threshold: 3
emits:
- tactic: TA0006
technique_id: T1110
sub_technique_id: T1110.003
confidence: 0.9
evidence_fields:
- shared_password_hash
- account_count

18
rules/ttp/R0004.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0004
rule_version: 1
name: credential_stuffing
description: |
Reused credential signal — CredentialLifter (E.3.13) consumes
CredentialReuse rows.
applies_to:
- credential
match:
kind: lifter:credential_reuse
emits:
- tactic: TA0006
technique_id: T1110
sub_technique_id: T1110.004
confidence: 0.9
evidence_fields:
- credential_hash
- reuse_count

18
rules/ttp/R0005.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0005
rule_version: 1
name: valid_account_use
description: |
Successful authentication on a previously-brute-forced account.
BehavioralLifter (E.3.9).
applies_to:
- auth_attempt
match:
kind: lifter:valid_account_use
require_prior_brute: true
emits:
- tactic: TA0001
technique_id: T1078
confidence: 0.7
evidence_fields:
- username
- service

25
rules/ttp/R0006.yaml Normal file
View File

@@ -0,0 +1,25 @@
rule_id: R0006
rule_version: 1
name: default_credentials
description: |
Login attempt with a known default credential pair (root/root,
admin/admin, etc.). BehavioralLifter (E.3.9) reads credentials
table.
applies_to:
- auth_attempt
match:
kind: lifter:default_credentials
pairs:
- [root, root]
- [admin, admin]
- [admin, password]
- [root, ""]
- [pi, raspberry]
emits:
- tactic: TA0001
technique_id: T1078
sub_technique_id: T1078.001
confidence: 0.9
evidence_fields:
- username
- service

24
rules/ttp/R0007.yaml Normal file
View File

@@ -0,0 +1,24 @@
rule_id: R0007
rule_version: 1
name: sqlmap_user_agent
description: |
sqlmap's default User-Agent header. Triggers on the raw URL
payload because the v0 engine's http_request default field is
raw_url; we override to user_agent. Same matcher catches nikto,
nmap-scripts, and other auto-tooling that brands itself in UA.
applies_to:
- http_request
match:
field: user_agent
pattern: '(?i)\b(sqlmap|nikto|w3af|acunetix|nessus|openvas|wpscan|dirbuster)\b'
emits:
- tactic: TA0001
technique_id: T1190
confidence: 0.9
- tactic: TA0043
technique_id: T1595
sub_technique_id: T1595.002
confidence: 0.9
evidence_fields:
- user_agent
- raw_url

18
rules/ttp/R0008.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0008
rule_version: 1
name: log4j_jndi
description: |
Log4j JNDI injection — ${jndi:ldap://...} pattern in any header
or URL component.
applies_to:
- http_request
match:
field: raw_url
pattern: '\$\{(?:jndi|\${[^}]*}):(?:ldap|ldaps|rmi|dns|http)s?://'
emits:
- tactic: TA0001
technique_id: T1190
confidence: 0.95
evidence_fields:
- raw_url
- headers

17
rules/ttp/R0009.yaml Normal file
View File

@@ -0,0 +1,17 @@
rule_id: R0009
rule_version: 1
name: path_traversal
description: |
Classic ../ traversal in URL path or query. Catches both raw and
URL-encoded forms (%2e%2e/, %2E%2E%2F).
applies_to:
- http_request
match:
field: raw_url
pattern: '(?i)(?:\.\./|%2e%2e/|\.\.%2f|%2e%2e%2f){2,}'
emits:
- tactic: TA0001
technique_id: T1190
confidence: 0.85
evidence_fields:
- raw_url

19
rules/ttp/R0010.yaml Normal file
View File

@@ -0,0 +1,19 @@
rule_id: R0010
rule_version: 1
name: unix_shell_exec
description: |
Reverse shell or shell-execution patterns: bash -i, /dev/tcp/,
nc -e, sh -c with exec primitives. Tight enough to skip plain
scripts; the broader T1059 catch is R0011.
applies_to:
- command
match:
field: command_text
pattern: '(?i)(?:bash\s+-i|/dev/tcp/|/dev/udp/|nc\s+-e\s|/bin/sh\s+-c\b|/bin/bash\s+-c\b)'
emits:
- tactic: TA0002
technique_id: T1059
sub_technique_id: T1059.004
confidence: 0.9
evidence_fields:
- command_text

18
rules/ttp/R0011.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0011
rule_version: 1
name: scripting_interpreter_exec
description: |
Generic command-and-scripting-interpreter signal — python -c,
perl -e, ruby -e, node -e, bash -c, php -r. Sub-technique-less
T1059 catch-all that complements R0010 (Unix-specific).
applies_to:
- command
match:
field: command_text
pattern: '(?i)\b(python[23]?|perl|ruby|node|php)\s+-[ce]\b|/bin/bash\s+-c\b|/bin/sh\s+-c\b'
emits:
- tactic: TA0002
technique_id: T1059
confidence: 0.7
evidence_fields:
- command_text

17
rules/ttp/R0012.yaml Normal file
View File

@@ -0,0 +1,17 @@
rule_id: R0012
rule_version: 1
name: ingress_tool_transfer
description: |
wget/curl/tftp/scp pulling a payload from a remote URL. Anchors
on the verb-then-URL shape; bare 'curl' alone won't fire.
applies_to:
- command
match:
field: command_text
pattern: '(?i)\b(wget|curl|tftp|scp|ftpget)\s+(?:-\S+\s+)*(?:[^|;\s]+\s+)*https?://|\b(wget|curl)\s+-O\s'
emits:
- tactic: TA0011
technique_id: T1105
confidence: 0.85
evidence_fields:
- command_text

17
rules/ttp/R0013.yaml Normal file
View File

@@ -0,0 +1,17 @@
rule_id: R0013
rule_version: 1
name: etc_passwd_read
description: |
cat / less / more / head / tail of /etc/passwd — classic
post-foothold discovery primitive.
applies_to:
- command
match:
field: command_text
pattern: '\b(cat|less|more|head|tail|nl|grep)\s+(?:[^|;]*\s)?/etc/passwd\b'
emits:
- tactic: TA0007
technique_id: T1083
confidence: 0.85
evidence_fields:
- command_text

18
rules/ttp/R0014.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0014
rule_version: 1
name: etc_shadow_read
description: |
Read of /etc/shadow — credential-dumping primitive, requires
root, much higher signal than passwd.
applies_to:
- command
match:
field: command_text
pattern: '\b(cat|less|more|head|tail|nl|grep|sudo\s+cat)\s+(?:[^|;]*\s)?/etc/shadow\b'
emits:
- tactic: TA0006
technique_id: T1003
sub_technique_id: T1003.008
confidence: 0.95
evidence_fields:
- command_text

22
rules/ttp/R0015.yaml Normal file
View File

@@ -0,0 +1,22 @@
rule_id: R0015
rule_version: 1
name: suid_search
description: |
find with -perm -u=s / -4000 / /4000 — explicit SUID-binary
hunting for privilege escalation. Two emits: discovery (T1083)
and the priv-esc abuse precursor (T1548.001).
applies_to:
- command
match:
field: command_text
pattern: '\bfind\b.*-perm\s+(?:-?u\+?=s|-?4000|/4000|-?2000)'
emits:
- tactic: TA0007
technique_id: T1083
confidence: 0.85
- tactic: TA0004
technique_id: T1548
sub_technique_id: T1548.001
confidence: 0.95
evidence_fields:
- command_text

18
rules/ttp/R0016.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0016
rule_version: 1
name: recursive_find
description: |
Generic recursive find rooted at /, /home, /etc, /var, /opt,
/root, /tmp — broad file/directory discovery. Lower confidence
than R0015 because non-malicious admin sweeps look the same.
applies_to:
- command
match:
field: command_text
pattern: '\bfind\s+/(?:home|etc|var|opt|root|tmp|usr)?(?=\s|/|$)'
emits:
- tactic: TA0007
technique_id: T1083
confidence: 0.65
evidence_fields:
- command_text

20
rules/ttp/R0017.yaml Normal file
View File

@@ -0,0 +1,20 @@
rule_id: R0017
rule_version: 1
name: network_service_scan
description: |
Scanner invocation: nmap, masscan, zmap, rustscan, unicornscan,
hping3 in scan mode, or nc -zv sweeps.
applies_to:
- command
match:
field: command_text
pattern: '(?i)\b(nmap|masscan|zmap|rustscan|unicornscan|hping3)\b|\bnc\s+(?:-\w*z\w*|-zv|-vz)\b'
emits:
- tactic: TA0007
technique_id: T1046
confidence: 0.9
- tactic: TA0043
technique_id: T1595
confidence: 0.9
evidence_fields:
- command_text

17
rules/ttp/R0018.yaml Normal file
View File

@@ -0,0 +1,17 @@
rule_id: R0018
rule_version: 1
name: system_info_discovery
description: |
uname / lsb_release / hostnamectl / cat /etc/os-release —
classic system-information gathering.
applies_to:
- command
match:
field: command_text
pattern: '\b(?:uname\s+-\w+|lsb_release(?:\s|$)|hostnamectl(?:\s|$))\b|cat\s+/etc/(?:os-release|issue|debian_version|redhat-release)\b'
emits:
- tactic: TA0007
technique_id: T1082
confidence: 0.7
evidence_fields:
- command_text

18
rules/ttp/R0019.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0019
rule_version: 1
name: user_discovery
description: |
whoami / id / w / who / last / users — current-user / logged-in
user enumeration. Word-boundary anchored at start so a substring
inside a longer command doesn't trip.
applies_to:
- command
match:
field: command_text
pattern: '^(?:\s*sudo\s+)?(?:whoami|id|w|who|users|last)(?:\s|$)'
emits:
- tactic: TA0007
technique_id: T1033
confidence: 0.7
evidence_fields:
- command_text

17
rules/ttp/R0020.yaml Normal file
View File

@@ -0,0 +1,17 @@
rule_id: R0020
rule_version: 1
name: network_config_discovery
description: |
ip addr / ifconfig / route / arp / iwconfig — network-interface
enumeration.
applies_to:
- command
match:
field: command_text
pattern: '\b(?:ip\s+(?:a|addr|link|route|-c\s+addr)|ifconfig(?:\s+-\w+)?|route\s+-\w+|arp\s+-\w+|iwconfig)\b'
emits:
- tactic: TA0007
technique_id: T1016
confidence: 0.75
evidence_fields:
- command_text

16
rules/ttp/R0021.yaml Normal file
View File

@@ -0,0 +1,16 @@
rule_id: R0021
rule_version: 1
name: network_connections_discovery
description: |
netstat / ss / lsof -i — active connection enumeration.
applies_to:
- command
match:
field: command_text
pattern: '\b(?:netstat\s+-\w+|ss\s+-\w+|lsof\s+-i\b)'
emits:
- tactic: TA0007
technique_id: T1049
confidence: 0.75
evidence_fields:
- command_text

21
rules/ttp/R0022.yaml Normal file
View File

@@ -0,0 +1,21 @@
rule_id: R0022
rule_version: 1
name: ldap_account_discovery
description: |
ldapsearch / BloodHound CLI / ADExplorer — LDAP-based account
and trust enumeration.
applies_to:
- command
match:
field: command_text
pattern: '(?i)\b(?:ldapsearch|bloodhound-?python|sharphound|adexplorer)\b'
emits:
- tactic: TA0007
technique_id: T1087
sub_technique_id: T1087.002
confidence: 0.9
- tactic: TA0007
technique_id: T1482
confidence: 0.85
evidence_fields:
- command_text

16
rules/ttp/R0023.yaml Normal file
View File

@@ -0,0 +1,16 @@
rule_id: R0023
rule_version: 1
name: smb_share_discovery
description: |
smbclient -L / enum4linux / nbtscan / rpcclient share-listing.
applies_to:
- command
match:
field: command_text
pattern: '(?i)\bsmbclient\s+-L\b|\benum4linux\b|\bnbtscan\b|\brpcclient\b.*\b(?:enumdomusers|netshareenum|querydispinfo)\b'
emits:
- tactic: TA0007
technique_id: T1135
confidence: 0.9
evidence_fields:
- command_text

18
rules/ttp/R0024.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0024
rule_version: 1
name: local_account_creation
description: |
useradd / adduser / direct write to /etc/passwd creating a new
local account — persistence primitive.
applies_to:
- command
match:
field: command_text
pattern: '\b(?:useradd|adduser)\s+(?:-\S+\s+)*\w+|echo\s+[^\n]*>>\s*/etc/passwd\b'
emits:
- tactic: TA0003
technique_id: T1136
sub_technique_id: T1136.001
confidence: 0.95
evidence_fields:
- command_text

18
rules/ttp/R0025.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0025
rule_version: 1
name: cron_persistence
description: |
Cron-based persistence: crontab -e, writes to /etc/cron.* or
/var/spool/cron/.
applies_to:
- command
match:
field: command_text
pattern: '\bcrontab\s+-e\b|>>?\s*/etc/cron\.\w+/|>>?\s*/var/spool/cron/'
emits:
- tactic: TA0003
technique_id: T1053
sub_technique_id: T1053.003
confidence: 0.9
evidence_fields:
- command_text

20
rules/ttp/R0026.yaml Normal file
View File

@@ -0,0 +1,20 @@
rule_id: R0026
rule_version: 1
name: redis_ssh_key_persistence
description: |
redis-cli / nc abuse setting CONFIG dir to /root/.ssh +
writing an authorized_keys SET. Per-command match; the lifter
composes them across a session, but either single command in
isolation still scores the technique.
applies_to:
- command
match:
field: command_text
pattern: '(?i)\bredis(?:-cli)?\b.*\b(?:config\s+set\s+dir|set\s+\S+\s+["'']?ssh-(?:rsa|ed25519|dss))\b'
emits:
- tactic: TA0003
technique_id: T1098
sub_technique_id: T1098.004
confidence: 0.9
evidence_fields:
- command_text

19
rules/ttp/R0027.yaml Normal file
View File

@@ -0,0 +1,19 @@
rule_id: R0027
rule_version: 1
name: webshell_install
description: |
Drop a PHP/JSP/ASPX webshell into a webroot via shell redirect
or wget/curl-to-file. Conservative — we want the shell pattern
AND a webroot path, not just any echo > x.php.
applies_to:
- command
match:
field: command_text
pattern: '(?:echo|printf|cat\s*<<\w+|wget\s+-O|curl\s+-o)\s+[^\n;|]*(?:<\?php|<%@|system\(|eval\(|exec\()[^\n;|]*>\s*[^\n;|]*\.(?:php|jsp|aspx|jspx|phtml)\b|>\s*/var/www/[^\s;|]+\.(?:php|jsp|aspx|jspx)\b'
emits:
- tactic: TA0003
technique_id: T1505
sub_technique_id: T1505.003
confidence: 0.9
evidence_fields:
- command_text

18
rules/ttp/R0028.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0028
rule_version: 1
name: clear_command_history
description: |
history -c / -cw, unset HISTFILE, redirect /dev/null over
~/.bash_history.
applies_to:
- command
match:
field: command_text
pattern: '(?i)\bhistory\s+-c\w*\b|\bunset\s+HISTFILE\b|>\s*~?/?\.bash_history\b|export\s+HISTFILE=/dev/null'
emits:
- tactic: TA0005
technique_id: T1070
sub_technique_id: T1070.003
confidence: 0.9
evidence_fields:
- command_text

18
rules/ttp/R0029.yaml Normal file
View File

@@ -0,0 +1,18 @@
rule_id: R0029
rule_version: 1
name: sudo_abuse
description: |
sudo -l (enumerate available privileged commands) or
sudo su / sudo -i / sudo -s for an interactive privilege escalation.
applies_to:
- command
match:
field: command_text
pattern: '^(?:\s*)sudo\s+(?:-l\b|-i\b|-s\b|su\b)'
emits:
- tactic: TA0004
technique_id: T1548
sub_technique_id: T1548.003
confidence: 0.75
evidence_fields:
- command_text

27
rules/ttp/R0030.yaml Normal file
View File

@@ -0,0 +1,27 @@
rule_id: R0030
rule_version: 1
name: jarm_hassh_c2_fingerprint
description: |
JARM/HASSH fingerprint match against the known-C2 catalogue.
Sniffer-side; populated as an enrichment, then the lifter
emits this rule's tag. v0 RuleEngine cannot interpret the
fingerprint blob.
applies_to:
- session
match:
kind: lifter:c2_fingerprint
catalogues:
- jarm
- hassh
emits:
- tactic: TA0011
technique_id: T1071
confidence: 0.85
- tactic: TA0011
technique_id: T1071
sub_technique_id: T1071.001
confidence: 0.9
evidence_fields:
- jarm
- hassh
- matched_framework