8.21. SSH Keywords

Suricata has several rule keywords to match on different elements of SSH connections.

8.21.1. Hooks

The SSH parser exposes a per-direction state machine. A rule that hooks one of the states below is evaluated once the transaction's progress in that direction has reached the state; for firewall rules the verdict applies while the transaction stays at the hooked state and stops applying once it advances past it (see app-layer-state).

Request (to_server) side:

State

Meaning

request_banner

Version exchange: the banner (version) phase; the first packets of the direction. A banner line of 256 bytes or more without an end-of-line keeps the direction here: the line is consumed greedily until the end-of-line, and the condition is observable through the ssh.long_banner event (the banner data is published when the long line first parses).

request_kex

A valid banner line was parsed; key exchange. ssh.proto, ssh.software and the hassh buffers are registered at this state. A long banner can publish them while the line is still open; if the line never completes, the values are not inspected at a clean flow end (the direction stays below the registered state); a flow that ends disrupted (depth truncation or async reassembly) reads as past this state, and the values are inspected there

request_session

A new keys record was seen in this direction: the session is established for that direction; the two directions are tracked independently (a parse failure in either ends the flow's app-layer parsing, see Completion and failure). new keys is a transition rather than a phase with its own data: match the record itself with a frame rule (message code 21, see the example below).

Response (to_client) side:

State

Meaning

response_banner

Server banner (version) phase: the same behavior as request_banner above, in the server-to-client direction.

response_kex

Server key exchange: same as request_kex — the direction's ssh.proto / ssh.software and hassh buffers are registered at this state.

response_session

A new keys record was seen in the server-to-client direction: same as request_session.

8.21.1.1. Overly long banner lines

A banner line of 256 bytes or more without an end-of-line keeps the direction in the banner state. The line is consumed greedily until the end-of-line arrives, and the direction then advances to kex. The condition is observable through the ssh.long_banner app-layer event (the banner data is published when the long line first parses). If the line does not parse as a banner when it completes, the direction fails and is frozen in banner.

8.21.1.2. Completion and failure

request_done / response_done is the registered completion state. No direction ever reports it: a successful direction tops out at session, and a direction that hits an unrecoverable error — an invalid banner or an invalid record (header or payload) — is frozen in the state the error occurred in. It is the last progress the direction reports, and the reason is available through the ssh.invalid_banner / ssh.invalid_record app-layer events and the per-direction error / state fields of the eve ssh object (invalid_banner / invalid_record and the state the failure occurred in; a failed flow is logged even when no banner was parsed, so the error field may be its only content). A parse failure puts the flow into an error state: nothing after the failing packet is parsed or delivered, mid-flow or at the flow-end flush, and the frozen state is what the progress accessor - and thus the flow-end eve object - reports. The ssh.invalid_banner / ssh.invalid_record events are evaluated as soon as the tx is next inspected - the failing packet itself unless the error policy skips it - at the latest the flow-end flush. A keyword registered at the failed state gets no eof run (eof requires progress above the registered state), so its flow-end evaluation stays non-terminal; states before it were already terminalized when the direction left them. The ssh.long_banner and ssh.long_kex_record events are non-fatal: the direction continues (a long banner keeps it in banner until the line completes; an oversized key-exchange record keeps it in kex until the record has been buffered in full).

Consequences worth knowing:

  • ssh:request_complete (and the firewall policy key request-complete) is bound to the completion state, which no direction reports: the hook matches nothing for well-formed flows. A disrupted flow (depth truncation or async reassembly) matches, because on a disrupted flow the engine reports the completion state as the transaction progress instead of querying the parser. Before this change it matched a flow whose session was established. Review existing accept:* ssh:request_complete rules and request-complete firewall policies: a session-admit rule like that no longer fires. Failures are reported through the ssh.invalid_banner / ssh.invalid_record events instead.

  • Rules cannot reference done (request_done / response_done do not load): it is out of both name tables. The generic hook lists for the completion state are registered under the built-in completion names (ssh:request_complete:generic / ssh:response_complete:generic); the state-name-based lists are not registered for the completion id (the id-to-name lookup returns null). They run only at a disrupted flow end, where the engine reports the completion state as the transaction progress instead of querying the parser - so Lua hooks on ssh completion no longer fire for well-formed flows; before this change the accessor reported the terminal state once both directions had sent NewKeys.

  • app-layer-state:>request_session matches only directions that were disrupted past session (e.g. by depth truncation or async reassembly) — a successful request direction stops at request_session, and a failed one is frozen at the state it failed in (use the ssh.invalid_* events for that).

For firewall rules and app-layer-state users, Migrating from the old SSH state names maps the old hook names to the new phases and describes the behaviour changes.

8.21.2. Frames

The SSH parser supports the following frames:

  • ssh.record_hdr

  • ssh.record_data

  • ssh.record_pdu

These are header + data = pdu for SSH records, after the banner and before encryption. The SSH record header is 6 bytes long : 4 bytes length, 1 byte passing, 1 byte message code.

Example:

alert ssh any any -> any any (msg:"hdr frame new keys"; frame:ssh.record.hdr; content: "|15|"; endswith; bsize: 6; sid:2;)

This rule matches like Wireshark ssh.message_code == 0x15.

8.21.3. ssh.proto

Match on the version of the SSH protocol used. ssh.proto is a sticky buffer, and can be used as a fast pattern. ssh.proto replaces the previous buffer name: ssh_proto. You may continue to use the previous name, but it's recommended that existing rules be converted to use the new name.

Format:

ssh.proto;

Example:

alert ssh any any -> any any (msg:"match SSH protocol version"; ssh.proto; content:"2.0"; sid:1000010;)

The example above matches on SSH connections with SSH version 2.0.

8.21.4. ssh.software

Match on the software string from the SSH banner. ssh.software is a sticky buffer, and can be used as fast pattern.

Format:

ssh.software;

Example:

alert ssh any any -> any any (msg:"match SSH software string"; ssh.software; content:"openssh"; nocase; sid:1000020;)

The example above matches on SSH connections where the software string contains "openssh".

8.21.5. ssh.hassh

Match on hassh (md5 of hassh algorithms of client).

alert ssh any any -> any any (msg:"match hassh";

ssh.hassh; content:"ec7378c1a92f5a8dde7e8b7a1ddf33d1";sid:1000010;)

ssh.hassh is a 'sticky buffer'.

ssh.hassh can be used as fast_pattern.

8.21.6. ssh.hassh.string

Match on Hassh string (hassh algorithms of client).

alert ssh any any -> any any (msg:"match hassh-string";

ssh.hassh.string; content:"none,zlib@openssh.com,zlib"; sid:1000030;)

ssh.hassh.string is a 'sticky buffer'.

ssh.hassh.string can be used as fast_pattern.

8.21.7. ssh.hassh.server

Match on hassh (md5 of hassh algorithms of server).

alert ssh any any -> any any (msg:"match SSH hash-server";

ssh.hassh.server; content:"b12d2871a1189eff20364cf5333619ee"; sid:1000020;)

ssh.hassh.server is a 'sticky buffer'.

ssh.hassh.server can be used as fast_pattern.

8.21.8. ssh.hassh.server.string

Match on hassh string (hassh algorithms of server).

alert ssh any any -> any any (msg:"match SSH hash-server-string";

ssh.hassh.server.string; content:"umac-64-etm@openssh.com,umac-128-etm@openssh.com"; sid:1000040;)

ssh.hassh.server.string is a 'sticky buffer'.

ssh.hassh.server.string can be used as fast_pattern.