mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
216 lines
8.8 KiB
ReStructuredText
216 lines
8.8 KiB
ReStructuredText
Firewall Ruleset Examples
|
|
=========================
|
|
|
|
.. note:: In Suricata 8 the firewall mode is experimental and subject to change.
|
|
|
|
HTTP
|
|
----
|
|
|
|
In this example a simple HTTP ruleset will be shown. It will allow HTTP to flow
|
|
as long as:
|
|
|
|
- method is GET or POST
|
|
- User-Agent is "curl"
|
|
- Status code is 200.
|
|
|
|
It starts by allowing the TCP port 80 traffic.
|
|
|
|
::
|
|
|
|
accept:hook tcp:all any any <> any 80 (sid:10;)
|
|
|
|
The stream tracking combined with the default exception policy handling will enforce
|
|
a proper TCP handshake, etc.
|
|
|
|
The HTTP rules need to ``accept`` each state::
|
|
|
|
# allow traffic before the request line is complete
|
|
accept:hook http1:request_started any any -> any any (sid:100;)
|
|
# allow GET
|
|
accept:hook http1:request_line any any -> any any ( \
|
|
http.method; content:"GET"; sid:101;)
|
|
# or allow POST
|
|
accept:hook http1:request_line any any -> any any ( \
|
|
http.method; content:"POST"; sid:102;)
|
|
# allow User-Agent curl
|
|
accept:hook http1:request_headers any any -> any any ( \
|
|
http.user_agent; content:"curl"; sid:103;)
|
|
# allow the body, if any
|
|
accept:hook http1:request_body any any -> any any (sid:104;)
|
|
# allow trailers, if any
|
|
accept:hook http1:request_trailer any any -> any any (sid:105;)
|
|
# allow completion
|
|
accept:hook http1:request_complete any any -> any any (sid:106;)
|
|
|
|
# allow traffic before the response line is complete
|
|
accept:hook http1:response_started any any -> any any (sid:200;)
|
|
# allow the 200 ok stat code.
|
|
accept:hook http1:response_line any any -> any any ( \
|
|
http.stat_code; content:"200"; sid:201;)
|
|
# allow all other states
|
|
accept:hook http1:response_headers any any -> any any (sid:202;)
|
|
accept:hook http1:response_body any any -> any any (sid:203;)
|
|
accept:hook http1:response_trailer any any -> any any (sid:204;)
|
|
accept:hook http1:response_complete any any -> any any (sid:205;)
|
|
|
|
Each state needs an ``accept`` rule. Each state is evaluated at least once.
|
|
|
|
.. _firewall examples-default policies-http:
|
|
|
|
HTTP example with partially using default policies
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
In the example below: the config auto accepts various hooks, leaving just ``http1:request_line``,
|
|
``http1:request_headers`` and ``http1:response_line`` for the ruleset to accept.
|
|
|
|
::
|
|
|
|
firewall:
|
|
policies:
|
|
app:
|
|
http1:
|
|
request-started:
|
|
- "accept:hook"
|
|
request-line:
|
|
- "drop:flow"
|
|
- "alert"
|
|
request-headers:
|
|
- "drop:flow"
|
|
- "alert"
|
|
request-body:
|
|
- "accept:hook"
|
|
request-trailer:
|
|
- "accept:hook"
|
|
request-complete:
|
|
- "accept:hook"
|
|
|
|
response-started:
|
|
- "accept:hook"
|
|
response-line:
|
|
- "drop:flow"
|
|
- "alert"
|
|
response-headers:
|
|
- "accept:hook"
|
|
response-body:
|
|
- "accept:hook"
|
|
response-trailer:
|
|
- "accept:hook"
|
|
response-complete:
|
|
- "accept:hook"
|
|
|
|
|
|
::
|
|
|
|
# allow GET
|
|
accept:hook http1:request_line any any -> any any ( \
|
|
http.method; content:"GET"; sid:101;)
|
|
# or allow POST
|
|
accept:hook http1:request_line any any -> any any ( \
|
|
http.method; content:"POST"; sid:102;)
|
|
# allow User-Agent curl
|
|
accept:hook http1:request_headers any any -> any any ( \
|
|
http.user_agent; content:"curl"; sid:103;)
|
|
|
|
# allow the 200 ok stat code.
|
|
accept:hook http1:response_line any any -> any any ( \
|
|
http.stat_code; content:"200"; sid:201;)
|
|
|
|
|
|
TLS SNI with complex TCP rules
|
|
------------------------------
|
|
|
|
In this example the ``packet:filter`` rules will be more opinionated about the traffic::
|
|
|
|
# allow 3-way handshake
|
|
accept:hook tcp:all $HOME_NET any -> $EXTERNAL_NET 443 (flags:S; \
|
|
flow:not_established; flowbits:set,syn; sid:1;)
|
|
accept:hook tcp:all $EXTERNAL_NET 443 -> $HOME_NET any (flags:SA; \
|
|
flow:not_established; flowbits:isset,syn; flowbits:set,synack; sid:2;)
|
|
accept:hook tcp:all $HOME_NET any -> $EXTERNAL_NET 443 (flags:A; \
|
|
flow:not_established; flowbits:isset,synack; \
|
|
flowbits:unset,syn; flowbits:unset,synack; sid:3;)
|
|
# allow established
|
|
accept:hook tcp:all $HOME_NET any <> $EXTERNAL_NET 443 (flow:established; sid:4;)
|
|
|
|
Then on the TLS level this will be a TLS SNI firewall.
|
|
:doc:`tls-state-migration` covers migrating rules and config
|
|
keys from the pre-phase state names.
|
|
|
|
Again all the states need to be accepted. Only in the ``client_hello``
|
|
state will there be additional constraints::
|
|
|
|
accept:hook tls:client_started $HOME_NET any -> $EXTERNAL_NET any (sid:100;)
|
|
# allow the good sites
|
|
accept:flow tls:client_hello $HOME_NET any -> $EXTERNAL_NET any (tls.sni; \
|
|
pcre:"/^(suricata.io|oisf.net)$/R"; sid:101;)
|
|
# drop every ClientHello that carried an SNI the rule above does
|
|
# not allow; the SNI buffer is required, so a fragment still in
|
|
# flight (no SNI buffer yet) cannot match
|
|
drop:flow tls:client_hello $HOME_NET any -> $EXTERNAL_NET any (alert; tls.sni; \
|
|
pcre:!"/^(suricata.io|oisf.net)$/R"; sid:102;)
|
|
# hello packets that carry no verdict of their own: the in-flight
|
|
# fragments
|
|
accept:hook tls:client_hello $HOME_NET any -> $EXTERNAL_NET any (sid:103;)
|
|
# no accept at the certificate state: a hello without an SNI matches
|
|
# no SNI rule and is decided by the default policy of the states it
|
|
# walked over (the implicit drop:flow) - the same outcome the base
|
|
# release gave it at the hello-completion milestone. If you want to
|
|
# accept SNI-less hellos, add an explicit accept here and audit the
|
|
# relaxation.
|
|
accept:hook tls:client_data $HOME_NET any -> $EXTERNAL_NET any (sid:105;)
|
|
accept:hook tls:client_finished $HOME_NET any -> $EXTERNAL_NET any (sid:106;)
|
|
|
|
accept:hook tls:server_started $EXTERNAL_NET any -> $HOME_NET any (sid:200;)
|
|
accept:hook tls:server_hello $EXTERNAL_NET any -> $HOME_NET any (sid:201;)
|
|
accept:hook tls:server_cert $EXTERNAL_NET any -> $HOME_NET any (sid:202;)
|
|
accept:hook tls:server_data $EXTERNAL_NET any -> $HOME_NET any (sid:204;)
|
|
accept:hook tls:server_finished $EXTERNAL_NET any -> $HOME_NET any (sid:205;)
|
|
|
|
The ``client_hello`` state names the ClientHello being parsed: it is
|
|
entered at the first byte of the message, and its completion moves
|
|
the state to ``client_cert``. The SNI buffer is owned by the hello
|
|
state, so ``tls.sni`` rules must live in ``client_hello`` even though
|
|
the record
|
|
that completes the hello is decided at ``client_cert``: a flow that
|
|
matches the SNI accept is carried over by the flow-scoped accept (101)
|
|
and a flow whose SNI is not allowed is dropped by 102 on that same
|
|
record - the drop is rule-scoped and alerts, where the implicit
|
|
default policy would drop silently. The plain ``accept:hook`` at 103
|
|
carries the fragments in flight (the SNI buffer is not available
|
|
before the message completes, so it also cannot gate them). A
|
|
ClientHello that carries no SNI at all matches no SNI rule either -
|
|
and with no accept at the certificate state it is decided by the
|
|
default policy of the states it walked over (the implicit
|
|
drop:flow), the same outcome the base release gave it at the
|
|
hello-completion milestone. A flow whose hello is accepted by 101 is
|
|
carried by the flow-scoped accept: the flow action is checked before
|
|
any further firewall rule of a packet is evaluated, so the later
|
|
states of that flow never reach the implicit default.
|
|
``client_started`` covers the packets before the hello.
|
|
|
|
TLS SNI with auto-accept logic
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Rule that has the same effect as the 11 TLS rules above::
|
|
|
|
accept:flow tls:<client_hello $HOME_NET any -> $EXTERNAL_NET any (tls.sni; \
|
|
pcre:"/^(suricata.io|oisf.net)$/R"; sid:101;)
|
|
|
|
Explanation: ``accept:flow`` accepts all of the TLS flow from the moment the rule
|
|
has matched. The ``tls:client_started`` hook is auto-accepted by the use of the
|
|
``<`` modifier in the hook ``tls:<client_hello``. The ``<`` modifier is only
|
|
valid for firewall rules: a threat detection rule using it fails to load.
|
|
|
|
TLS SNI with auto-accept logic, plus disabling TD matching
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
To allow-list a connection to a specific SNI, w/o threat detection rules
|
|
matching on this flow either, the example above can be extended by adding ``pass:flow``
|
|
as a secondary action::
|
|
|
|
accept:flow,pass:flow tls:<client_hello $HOME_NET any -> $EXTERNAL_NET any \
|
|
(tls.sni; pcre:"/^(suricata.io|oisf.net)$/; sid:101;)
|
|
|
|
Explanation: as soon as this rule fully matches at the ``tls:client_hello`` hook,
|
|
a ``pass`` is applied to the flow effectively bypassing the threat detection engine.
|