detect: extend app-layer-protocol to accept a pipe-separated value list

Extend the app-layer-protocol keyword to accept a pipe-separated list of
protocol values, so a single rule can match any of several protocols:

    app-layer-protocol:[!]<proto1>|<proto2>[|...][,<qualifier>]...;

A non-negated list matches when the flow's protocol equals any listed value
(OR); a negated list matches when it equals none of them (NOR). The
single-value form and the trailing mode qualifier are unchanged.

Matching keeps the historical AppProtoEquals() equivalences by default
(dns/doh2, http/http1/http2, dcerpc/smb, ...). An `exact` qualifier selects
strict identity matching with no equivalences and no http umbrella; it
combines with a direction mode in any order. Because a flow is never the
generic ALPROTO_HTTP, `http,exact` is rejected at load.

Values are expanded once at rule load into an effective match-set bitmask, so
the per-packet match is a single bitmask test. Single-value rules remain
prefilterable; multi-value rules are excluded from prefiltering and an
explicit prefilter on them is rejected. Conflicting keyword combinations
(duplicate or overlapping negations, mixed positive/negated) are rejected at
load. Engine-analysis reports the effective match set.

Ticket: 7705
(cherry picked from commit 43bc2db41e)
pull/16202/head
Yash Datre 2 months ago committed by Victor Julien
parent 8b0775f557
commit 353885acc2

@ -10,7 +10,11 @@ Match on the detected app-layer protocol.
Syntax:: Syntax::
app-layer-protocol:[!]<protocol>(,<mode>); app-layer-protocol:[!]<protocol>[,<qualifier>]...;
app-layer-protocol:[!]<proto1>|<proto2>[|...|<protoN>][,<qualifier>]...;
Each ``<qualifier>`` is either a ``<mode>`` (at most one, see below) or the
``exact`` option, in any order.
Examples:: Examples::
@ -21,6 +25,12 @@ Examples::
app-layer-protocol:http,to_server; app-layer-protocol:tls,to_client; app-layer-protocol:http,to_server; app-layer-protocol:tls,to_client;
app-layer-protocol:http2,final; app-layer-protocol:http1,original; app-layer-protocol:http2,final; app-layer-protocol:http1,original;
app-layer-protocol:unknown; app-layer-protocol:unknown;
app-layer-protocol:unknown|tls;
app-layer-protocol:unknown|tls|http;
app-layer-protocol:!tls|http;
app-layer-protocol:tls|http,either;
app-layer-protocol:dns,exact;
app-layer-protocol:tls|dns,either,exact;
A special value 'failed' can be used for matching on flows in which A special value 'failed' can be used for matching on flows in which
protocol detection failed. This can happen if Suricata doesn't know protocol detection failed. This can happen if Suricata doesn't know
@ -42,12 +52,92 @@ By default, (if no mode is specified), the mode is ``direction``.
.. note:: when negation is used, like ``!http``, it will not match on the .. note:: when negation is used, like ``!http``, it will not match on the
"unknown" state in the flow. "unknown" state in the flow.
Protocol equivalences and the ``exact`` option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default a value matches its related protocols as well as itself. For
example ``http`` matches ``http1`` and ``http2``, ``dns`` also matches
``doh2`` (DNS over HTTP/2), and ``dcerpc`` also matches ``smb``. This is the
long-standing behaviour and keeps existing rules working.
Add the ``exact`` qualifier to match strictly, with no equivalences: the
flow's protocol must equal the configured value exactly. ``exact`` applies to
all values in the list and can be combined with a mode::
app-layer-protocol:dns,exact; # matches dns only, not doh2
app-layer-protocol:tls|dns,either,exact;
Because ``exact`` disables all equivalences, the generic ``http`` value is not
expanded to ``http1``/``http2`` either. A flow is never classified as the
generic ``http``, so ``app-layer-protocol:http,exact`` can never match and is
rejected at rule load; use ``http1`` or ``http2`` instead.
Here is an example of a rule matching non-http traffic on port 80: Here is an example of a rule matching non-http traffic on port 80:
.. container:: example-rule .. container:: example-rule
alert tcp any any -> any 80 (msg:"non-HTTP traffic over HTTP standard port"; flow:to_server; app-layer-protocol:!http,final; sid:1; ) alert tcp any any -> any 80 (msg:"non-HTTP traffic over HTTP standard port"; flow:to_server; app-layer-protocol:!http,final; sid:1; )
Multi-value form
~~~~~~~~~~~~~~~~
The ``app-layer-protocol`` keyword also accepts a pipe-separated (``|``) list
of protocol values. A rule matches when the flow's resolved application-layer
protocol equals **any** value in the list (logical OR).
Syntax::
app-layer-protocol:[!]<proto1>|<proto2>[|...|<protoN>](,<mode>);
Using ``|`` for the list keeps the optional trailing ``,<mode>`` qualifier
unambiguous, so the single-value ``<protocol>,<mode>`` form is unchanged.
Examples::
app-layer-protocol:unknown|tls;
app-layer-protocol:unknown|tls|http;
app-layer-protocol:tls|http,either;
app-layer-protocol:!tls|http;
The ``unknown|<proto>`` detection-window idiom
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When Suricata has not yet classified a flow's protocol (the "detection
window"), the flow's app-layer protocol is ``unknown``. Once protocol
detection completes, the protocol transitions to its classified value
(e.g., ``tls``, ``http``). Including ``unknown`` in a multi-value list
allows a single rule to cover both the detection window and the confirmed
protocol::
app-layer-protocol:unknown|tls;
This rule matches during the detection window (while the protocol is still
``unknown``) **and** after classification (when the protocol is ``tls``).
If the flow is classified to a protocol not in the list (e.g., ``http``),
the rule stops matching once the protocol is classified; in firewall mode the
flow is then handled by the default policy if no other rule accepts it.
Negated multi-value (NOR semantics)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When the multi-value form is negated with ``!``, it implements NOR semantics
across the entire list: the rule matches when the resolved application-layer
protocol is **known** AND matches **none** of the listed values.
Example::
app-layer-protocol:!tls|http;
This matches when the flow's protocol is known and is neither ``tls`` nor
``http`` (e.g., it matches ``dns``, ``ssh``, ``smtp``, etc.).
.. note:: Negated multi-value rules do not match during the detection window
(when the protocol is still ``unknown``). This prevents false positives
before protocol classification is complete.
.. note:: The value ``unknown`` cannot appear in a negated list. The parser
rejects ``!unknown`` and ``!unknown|tls`` at rule-load time.
.. _proto-detect-bail-out: .. _proto-detect-bail-out:
Bail out conditions Bail out conditions

File diff suppressed because it is too large Load Diff

@ -24,6 +24,31 @@
#ifndef SURICATA_DETECT_APP_LAYER_PROTOCOL__H #ifndef SURICATA_DETECT_APP_LAYER_PROTOCOL__H
#define SURICATA_DETECT_APP_LAYER_PROTOCOL__H #define SURICATA_DETECT_APP_LAYER_PROTOCOL__H
#include "app-layer-protos.h"
void DetectAppLayerProtocolRegister(void); void DetectAppLayerProtocolRegister(void);
const char *DetectAppLayerProtocolModeName(uint8_t mode);
struct DetectAppLayerProtocolData_;
uint16_t DetectAppLayerProtocolGetValues(
const struct DetectAppLayerProtocolData_ *data, AppProto *out, uint16_t max);
/**
* \brief Per-rule keyword data for `app-layer-protocol:`.
*
* `alprotos` is the effective match set: a bitmask (one bit per AppProto,
* sized g_alproto_max) holding every flow protocol that should match, with the
* AppProtoEquals() equivalences (or, with the `exact` option, only the exact
* values) already expanded in at rule load. The per-packet match is then a
* single bitmask test. `alproto` is the first configured value, used as the
* prefilter bucket key for single-value (prefilterable) rules.
*/
typedef struct DetectAppLayerProtocolData_ {
AppProto alproto; /**< first configured value; single-value prefilter key */
bool negated;
bool exact; /**< `exact` option: strict identity, no equivalences/umbrella */
bool is_list; /**< more than one value configured (not prefilterable) */
uint8_t mode;
uint8_t *alprotos; /**< effective match set (g_alproto_max bits) */
} DetectAppLayerProtocolData;
#endif /* SURICATA_DETECT_APP_LAYER_PROTOCOL__H */ #endif /* SURICATA_DETECT_APP_LAYER_PROTOCOL__H */

@ -54,6 +54,7 @@
#include "util-var-name.h" #include "util-var-name.h"
#include "detect-icmp-id.h" #include "detect-icmp-id.h"
#include "detect-tcp-window.h" #include "detect-tcp-window.h"
#include "detect-app-layer-protocol.h"
static int rule_warnings_only = 0; static int rule_warnings_only = 0;
@ -975,6 +976,21 @@ static void DumpMatches(RuleAnalyzer *ctx, SCJsonBuilder *js, const SigMatchData
SCJbClose(js); SCJbClose(js);
break; break;
} }
case DETECT_APP_LAYER_PROTOCOL: {
const DetectAppLayerProtocolData *ad = (const DetectAppLayerProtocolData *)smd->ctx;
SCJbOpenObject(js, "app_layer_protocol");
AppProto vals[256];
uint16_t n = DetectAppLayerProtocolGetValues(ad, vals, ARRAY_SIZE(vals));
SCJbOpenArray(js, "protocols");
for (uint16_t i = 0; i < n; i++) {
SCJbAppendString(js, AppProtoToString(vals[i]));
}
SCJbClose(js);
SCJbSetString(js, "mode", DetectAppLayerProtocolModeName(ad->mode));
SCJbSetBool(js, "negated", ad->negated);
SCJbClose(js);
break;
}
} }
SCJbClose(js); SCJbClose(js);

@ -29,6 +29,7 @@
#include "detect.h" #include "detect.h"
#include "detect-parse.h" #include "detect-parse.h"
#include "detect-content.h" #include "detect-content.h"
#include "detect-app-layer-protocol.h"
#include "detect-engine-mpm.h" #include "detect-engine-mpm.h"
#include "detect-prefilter.h" #include "detect-prefilter.h"
#include "util-debug.h" #include "util-debug.h"
@ -107,6 +108,16 @@ static int DetectPrefilterSetup (DetectEngineCtx *de_ctx, Signature *s, const ch
SCReturnInt(-1); SCReturnInt(-1);
} }
/* A multi-value app-layer-protocol keyword stores its values in a
* bitmask that the single-valued prefilter bucket key can't carry, so
* forcing prefilter would bucket the rule under ALPROTO_UNKNOWN and
* silently never match. */
if (sm->type == DETECT_APP_LAYER_PROTOCOL &&
((const DetectAppLayerProtocolData *)sm->ctx)->is_list) {
SCLogError("prefilter is not supported for multi-value app-layer-protocol");
SCReturnInt(-1);
}
/* make sure setup function runs for this type. */ /* make sure setup function runs for this type. */
de_ctx->sm_types_prefilter[sm->type] = true; de_ctx->sm_types_prefilter[sm->type] = true;
} }

Loading…
Cancel
Save