openssh Gratuit
Force brute SSH, hôtes à clé seule compris — deux règles, une échelle.
Télécharger le pack tar.gz des fichiers listés ci-dessous
Règles (2)
-
sshSSH brute force — wrong passwords, connections dropped in authentication, invalid banners; one jail -
ssh-rootGuessing root's SSH password — two failures and out
À propos de ce pack
sshd, read from a syslog file (/var/log/auth.log) written in
RFC3339 — rsyslog's RSYSLOG_FileFormat, the Debian 12+ default:
2026-08-14T09:53:19.686208+02:00 host sshd[3168858]: Invalid user sftp_user from 203.0.113.9 port 38368
Category ssh. Every parser captures the line's own timestamp, so
-dry-run can judge a log's past.
The traditional format (Aug 14 09:53:19 host sshd-session[…]) is
read by the same parsers — the regexes accept either timestamp and
sshd or sshd-session (OpenSSH 9.8+, Debian 13) — but the line's own
time is not understood then (time_format is rfc3339), so the agent
dates each hit by the moment it read it: right for a live tail, wrong
for -dry-run on an old file. For that, set time_format: syslog in
copies of the parsers. Reading journald instead (unit: ssh.service)
means the message has no syslog prefix at all: drop the leading
^(?P<time>…) \S+ sshd(?:-session)?\[\d+\]: and the two time keys —
journald carries its own clock. The agent's shipped examples/ssh is
that variant.
Usernames are attacker-controlled
sshd logs the username it was given, and a username may contain spaces.
Every pattern here is therefore anchored at the end of the line, with
the username running greedily, so that from <address> port <n> binds
to the last occurrence — the one sshd wrote. Without that, connecting as
x from 198.51.100.1 port 22 ssh2 gets a bystander banned.
Keep that property in anything derived from these.
Parseurs (8)
Les briques : chaque parseur nomme un journal, un préfiltre peu coûteux et la regex qui extrait l'adresse.
ssh-auth-abandoned ssh
# An authentication that was under way for a NAMED account when the # client gave up: # Connection closed by authenticating user root 203.0.113.9 port 56390 [preauth] # Disconnected from invalid user vlad 203.0.113.9 port 57134 [preauth] # # This is a login attempt. sshd only writes "authenticating user X" once # a client has offered an account and the exchange has begun, so the # connection was not a port scan and not a monitor: someone tried, and # stopped when they did not get in. On a key-only host they were never # going to. # # Worth the same as a refused password, and the reason this parser # exists at all: it used to be folded into ssh-preauth-abort, where the # weight had to suit a bare dropped connection, and a real bot working # through root then scored one point out of five and walked. Keeping # the named-account lines apart is what lets each carry its own weight. # # The account name is chosen by the client, so the pattern is anchored # at the end of the line and lets it run greedily: " <address> port <n> # [preauth]" then binds to the last one on the line, which is sshd's. parser: ssh-auth-abandoned log: ssh prefilter: "[preauth]" regex: '^(?P<time>\S+(?: +\d+ \d\d:\d\d:\d\d)?) \S+ sshd(?:-session)?\[\d+\]: (?:Connection closed by|Connection reset by|Disconnected from) (?:authenticating user|invalid user) (?P<user>.*) (?P<ip>[0-9a-fA-F.:]+) port \d+ \[preauth\]$' time_field: time time_format: rfc3339
ssh-auth-fail ssh
# A password refused, for a real account or an invented one: # Failed password for root from 203.0.113.9 port 51266 ssh2 # Failed password for invalid user admin from 203.0.113.9 port 51266 ssh2 # # The username is chosen by whoever is connecting and may contain # spaces, so the pattern is anchored at the end of the line and lets the # username run greedily: " from <address> port <n> ssh2" then binds to # the last one on the line, which is the one sshd wrote. Without that, a # user called "x from 198.51.100.1 port 22 ssh2" gets someone else # banned. parser: ssh-auth-fail log: ssh prefilter: "Failed password" regex: '^(?P<time>\S+(?: +\d+ \d\d:\d\d:\d\d)?) \S+ sshd(?:-session)?\[\d+\]: Failed password for (?:invalid user )?(?P<user>.*) from (?P<ip>\S+) port \d+ ssh2$' time_field: time time_format: rfc3339
ssh-auth-timeout ssh
# A connection held open without authenticating until sshd gave up: # fatal: Timeout before authentication for 203.0.113.9 port 22 # # The signature of a regreSSHion (CVE-2024-6387) attempt, which needs # thousands of them, and of nothing a client does on purpose. parser: ssh-auth-timeout log: ssh prefilter: "Timeout before authentication" regex: '^(?P<time>\S+(?: +\d+ \d\d:\d\d:\d\d)?) \S+ sshd(?:-session)?\[\d+\]: .*Timeout before authentication for (?P<ip>[0-9a-fA-F.:]+) port \d+$' time_field: time time_format: rfc3339
ssh-banner-invalid ssh
# Something that is not an SSH client talking to the SSH port: # banner exchange: Connection from 203.0.113.9 port 50920: invalid format # # The companion line names what was sent ("MGLNDD_<host>_22", "GET / # HTTP/1.1") but carries no address, so this is the line worth reading. # Port scanners and internet-wide surveys produce nearly all of it. parser: ssh-banner-invalid log: ssh prefilter: "banner exchange" regex: '^(?P<time>\S+(?: +\d+ \d\d:\d\d:\d\d)?) \S+ sshd(?:-session)?\[\d+\]: banner exchange: Connection from (?P<ip>[0-9a-fA-F.:]+) port \d+: invalid format$' time_field: time time_format: rfc3339
ssh-invalid-user ssh
# A username that does not exist here: # Invalid user sftp_user from 203.0.113.9 port 38368 # # On a key-only host this is most of what an attack looks like: the # password never gets as far as being refused. parser: ssh-invalid-user log: ssh prefilter: "Invalid user" regex: '^(?P<time>\S+(?: +\d+ \d\d:\d\d:\d\d)?) \S+ sshd(?:-session)?\[\d+\]: Invalid user (?P<user>.*) from (?P<ip>\S+) port \d+$' time_field: time time_format: rfc3339
ssh-preauth-abort ssh
# A connection dropped before authentication, WITH NOBODY NAMED: # Connection closed by 203.0.113.9 port 47782 [preauth] # Disconnected from 203.0.113.9 port 57134 [preauth] # # Weak evidence on purpose. A port scanner writes these, and so does a # monitoring probe checking the port is open, and so does a phone on a # bad connection. Nothing says anyone tried to get in. # # The moment sshd names an account, it is a different line and a # different parser: see ssh-auth-abandoned, which is worth as much as a # refused password. Keeping them apart is the whole point — merged, the # weight had to suit the weaker one, and a real attempt on root scored # a single point. parser: ssh-preauth-abort log: ssh prefilter: "[preauth]" regex: '^(?P<time>\S+(?: +\d+ \d\d:\d\d:\d\d)?) \S+ sshd(?:-session)?\[\d+\]: (?:Connection closed by|Connection reset by|Disconnected from) (?P<ip>[0-9a-fA-F.:]+) port \d+ \[preauth\]$' time_field: time time_format: rfc3339
ssh-root-fail ssh
# A failed password FOR ROOT specifically. root is a real account, so # the line has no "invalid user" — a bot guessing root's password writes # "Failed password for root from <ip>". On a correctly run server root # either cannot log in over SSH at all or takes a key only: a password # attempt on root is never a fat-fingered admin, it is an attack, and it # earns a faster verdict than the general brute-force jail (Robin, # 2026-08-20: "ban au 2e essai"). The general ssh rule counts it too. parser: ssh-root-fail log: ssh prefilter: "Failed password for root from" regex: '^(?P<time>\S+(?: +\d+ \d\d:\d\d:\d\d)?) \S+ sshd(?:-session)?\[\d+\]: Failed password for root from (?P<ip>\S+) port \d+ ssh2$' time_field: time time_format: rfc3339
ssh-root-preauth ssh
# A root attempt that ended at preauth — "Connection closed/reset by # authenticating user root", "Disconnected from authenticating user # root". On a key-only host (no password accepted) this is the ONLY # trace a bot guessing root leaves: no "Failed password" is ever # written. root over SSH is never a legitimate password client, so this # counts toward the two-strike root jail exactly like a failed password # (Robin, 2026-08-20: the preauth lines were being missed). "root" # exactly — a username like "root2" reads as its own name, not this. parser: ssh-root-preauth log: ssh prefilter: "authenticating user root" regex: '^(?P<time>\S+(?: +\d+ \d\d:\d\d:\d\d)?) \S+ sshd(?:-session)?\[\d+\]: (?:Connection closed by|Connection reset by|Disconnected from) authenticating user root (?P<ip>[0-9a-fA-F.:]+) port \d+ \[preauth\]$' time_field: time time_format: rfc3339
Politiques de ban
ssh 6h → 24h → 7d
# # The ladder the ssh rule climbs. A first offence is six hours — sshd is # nobody's door but the administrator's, and an address that failed # three times in five minutes is not the administrator. One that comes # back after its ban ran out has proved it means it, and climbs; the # last rung repeats for ever. Quiet for thirty days after a ban ENDED # (never after it started) and the next offence starts at the bottom. # # tighten: each past episode within decay also halves the hits the rule # needs — the second visit convicts on 2 of 3, the third on 1. Remove # the line to keep the thresholds fixed. policy: ssh durations: [6h, 24h, 7d] ports: service memory: 30d tighten: 2x
Signalements
ssh-brute-force Unauthorised SSH login attempts
# # What a report about this says, if reporting is turned on at all. # # `include` is the redaction, and it is a list of what MAY leave rather # than a filter of what must not. A field this does not name cannot # appear in a report, whatever a log line happens to contain — which # matters here because the parsers capture the account names that were # tried, and those are none of a public database's business. Somebody # typed their own username into the wrong host once; that is not a # thing to publish under your name. # # The offender's address is always sent. It is what a report *is*. report: ssh-brute-force categories: [brute-force, ssh] says: "Unauthorised SSH login attempts" # Nothing else leaves. The sentence, the count and the window are # enough for anyone reading the report to know what happened — and the # provider's own guidance asks for exactly that rather than a log dump. include: []
Fichiers
- PACK.md
- rules/ssh-root.yaml
- rules/ssh.yaml
- parsers/ssh-auth-abandoned.yaml
- parsers/ssh-auth-fail.yaml
- parsers/ssh-auth-timeout.yaml
- parsers/ssh-banner-invalid.yaml
- parsers/ssh-invalid-user.yaml
- parsers/ssh-preauth-abort.yaml
- parsers/ssh-root-fail.yaml
- parsers/ssh-root-preauth.yaml
- policies/ssh.yaml
- reports/ssh-brute-force.yaml