Skip to main content

Shipped authentication rules

These are the three shipped backend development rules, including known false positives and their test cases. Read rule authoring before adapting them.

schema_version: 1

# The ruleset the local stack runs, so that `make up` detects something end to
# end. It is three rules and not a catalogue: each rule is a security decision,
# and a rule nobody chose is noise somebody has to turn off. The second counts
# and the third orders, because a threshold and a story are the two parts of a
# rule that one event cannot demonstrate on its own.
rules:
- id: ssh.failed_password_from_outside
revision: 1
name: Failed SSH password from outside the estate
description: >-
A password authentication over SSH failed for a session that reached the
host from an address outside the private ranges.
class: authentication
severity: medium
status: active
technique:
tactic: credential_access
id: T1110.001
name: "Brute Force: Password Guessing"
false_positives: >-
An administrator mistyping a password from a home connection. The rule
also treats 172.16.0.0/12 as external, because the language has no way to
say a range yet.
response: >-
Confirm the account is expected to be reachable from outside, then check
for a pattern from the same address.
tags: [ssh, credential_access]
references:
- https://attack.mitre.org/techniques/T1110/001/
match:
all:
- field: authentication.outcome
equals: failure
- field: authentication.service.protocol
equals: ssh
- not:
any:
- field: authentication.network.source.ip
starts_with: "10."
- field: authentication.network.source.ip
starts_with: "192.168."
- field: authentication.network.source.ip
starts_with: "127."
tests:
- name: a failed password from an address outside the estate
expect: match
severity: medium
evidence:
- authentication.outcome
- authentication.service.protocol
- authentication.network.source.ip
event:
authentication.outcome: failure
authentication.service.protocol: ssh
authentication.network.source.ip: 203.0.113.10

- name: the same failure from inside the estate
description: >-
The false positive the rule is written to avoid: a session from a
private range is not what it is looking for.
expect: no_match
event:
authentication.outcome: failure
authentication.service.protocol: ssh
authentication.network.source.ip: 10.0.0.5

- name: a password that was accepted
expect: no_match
event:
authentication.outcome: success
authentication.service.protocol: ssh
authentication.network.source.ip: 203.0.113.10

- name: a failure on another service
expect: no_match
event:
authentication.outcome: failure
authentication.service.protocol: rdp
authentication.network.source.ip: 203.0.113.10

- name: a failure from the loopback address
expect: no_match
event:
authentication.outcome: failure
authentication.service.protocol: ssh
authentication.network.source.ip: 127.0.0.1

- name: a failure the event carries no address for
description: >-
An absent field answers no question, so none of the private ranges
holds and the rule reads the session as external. Written down because
it is a decision the rule makes rather than one anybody made for it.
expect: match
severity: medium
event:
authentication.outcome: failure
authentication.service.protocol: ssh

- name: a failure from the private range the rule cannot express
description: >-
172.16.0.0/12 is private and the language has no way to say a range,
so the rule finds it. This is the false positive the guidance names.
expect: match
severity: medium
event:
authentication.outcome: failure
authentication.service.protocol: ssh
authentication.network.source.ip: 172.16.4.9

- id: ssh.repeated_failed_password
revision: 1
name: Repeated failed SSH passwords from one address
description: >-
Twenty password authentications over SSH failed for one source address
against one agent inside a minute, which is a rate no person types at.
class: authentication
severity: high
status: active
technique:
tactic: credential_access
id: T1110.001
name: "Brute Force: Password Guessing"
false_positives: >-
A misconfigured service account retrying a stale credential in a loop.
Check the user before the address: an automation retries as one account
and a guessing attempt walks through several.
response: >-
Block the source address at the edge, then confirm whether any of the
attempts succeeded by hunting the same address for a success in the hour
around the window.
tags: [ssh, credential_access, bruteforce]
references:
- https://attack.mitre.org/techniques/T1110/001/
count:
at_least: 20
within: 1m
group_by: [authentication.network.source.ip, origin.agent_id]
match:
all:
- field: authentication.outcome
equals: failure
- field: authentication.service.protocol
equals: ssh
tests:
# A case says what the rule reads of one event. How many such events make
# a detection is the engine's and is proven once, so what is written here
# is which events are counted rather than what the count comes to.
- name: a failed password over ssh is one the rule counts
expect: match
severity: high
evidence:
- authentication.outcome
- authentication.service.protocol
event:
authentication.outcome: failure
authentication.service.protocol: ssh
authentication.network.source.ip: 203.0.113.10

- name: a password that was accepted is not
expect: no_match
event:
authentication.outcome: success
authentication.service.protocol: ssh
authentication.network.source.ip: 203.0.113.10

- name: a failure on another service is not
expect: no_match
event:
authentication.outcome: failure
authentication.service.protocol: rdp
authentication.network.source.ip: 203.0.113.10

- name: a failure from inside the estate is counted too
description: >-
Unlike the rule above, this one does not care where the session came
from: twenty failures a minute from a host on the estate is a
compromised host rather than a person mistyping.
expect: match
severity: high
event:
authentication.outcome: failure
authentication.service.protocol: ssh
authentication.network.source.ip: 10.0.0.5

- id: ssh.password_guessing_that_succeeded
revision: 1
name: SSH password guessing that succeeded
description: >-
A password authentication over SSH failed for an address against an agent,
and then one from the same address against the same agent was accepted
inside five minutes. The failure alone is noise and the success alone is
somebody logging in; in that order they are a guess that worked.
class: authentication
severity: critical
status: active
technique:
tactic: credential_access
id: T1110.001
name: "Brute Force: Password Guessing"
false_positives: >-
A person who mistyped once and then got it right, which is the common case
and is why the rule wants both events against the same agent from the same
address rather than either on its own. Read the count of failures before
the success: one is a typo and forty is not.
response: >-
Treat the account as compromised until proven otherwise. The detection
names both events, so hunt the source address for what the session did
after it was accepted rather than for how it got in.
tags: [ssh, credential_access, bruteforce]
references:
- https://attack.mitre.org/techniques/T1110/001/
sequence:
within: 5m
group_by: [authentication.network.source.ip, origin.agent_id]
stages:
- name: a failed password
match:
all:
- field: authentication.outcome
equals: failure
- field: authentication.service.protocol
equals: ssh
- name: one that was accepted
match:
all:
- field: authentication.outcome
equals: success
- field: authentication.service.protocol
equals: ssh
tests:
# A case says what the rule reads of one event. Which events make a story
# and in what order is the engine's and is proven once, so what is written
# here is which events are part of one at all.
- name: a failed password over ssh opens the story
expect: match
severity: critical
evidence:
- authentication.outcome
- authentication.service.protocol
event:
authentication.outcome: failure
authentication.service.protocol: ssh
authentication.network.source.ip: 203.0.113.10

- name: a password that was accepted over ssh closes it
expect: match
severity: critical
evidence:
- authentication.outcome
- authentication.service.protocol
event:
authentication.outcome: success
authentication.service.protocol: ssh
authentication.network.source.ip: 203.0.113.10

- name: neither outcome on another service is part of it
expect: no_match
event:
authentication.outcome: failure
authentication.service.protocol: rdp
authentication.network.source.ip: 203.0.113.10

Source evidence

Generated from deploy/rules/authentication.yml at fa3bf69. Refresh the checked-in snapshot before regenerating; a normal build does not access another repository.