Bro relies primarily on its extensive scripting language for defining and analyzing detection policies. In addition, however, Bro also provides an independent signature language for doing low-level, Snort-style pattern matching. While signatures are not Bro’s preferred detection tool, they sometimes come in handy and are closer to what many people are familiar with from using other NIDS. This page gives a brief overview on Bro’s signatures and covers some of their technical subtleties.
Contents
Let’s look at an example signature first:
signature my-first-sig { ip-proto == tcp dst-port == 80 payload /.*root/ event "Found root!" }
This signature asks Bro to match the regular expression .*root
on
all TCP connections going to port 80. When the signature triggers, Bro
will raise an event signature_match
of the form:
event signature_match(state: signature_state, msg: string, data: string)
Here, state
contains more information on the connection that
triggered the match, msg
is the string specified by the
signature’s event statement (Found root!
), and data is the last
piece of payload which triggered the pattern match.
To turn such signature_match
events into actual alarms, you can
load Bro’s base/frameworks/signatures/main.bro script.
This script contains a default event handler that raises
Signatures::Sensitive_Signature
Notices
(as well as others; see the beginning of the script).
As signatures are independent of Bro’s policy scripts, they are put into
their own file(s). There are three ways to specify which files contain
signatures: By using the -s
flag when you invoke Bro, or by
extending the Bro variable signature_files
using the +=
operator, or by using the @load-sigs
directive inside a Bro script.
If a signature file is given without a full path, it is searched for
along the normal BROPATH
. Additionally, the @load-sigs
directive can be used to load signature files in a path relative to the
Bro script in which it’s placed, e.g. @load-sigs ./mysigs.sig
will
expect that signature file in the same directory as the Bro script. The
default extension of the file name is .sig
, and Bro appends that
automatically when necessary.
Let’s look at the format of a signature more closely. Each individual
signature has the format signature <id> { <attributes> }
. <id>
is a unique label for the signature. There are two types of
attributes: conditions and actions. The conditions define when the
signature matches, while the actions declare what to do in the case of
a match. Conditions can be further divided into four types: header,
content, dependency, and context. We discuss these all in more
detail in the following.
Header conditions limit the applicability of the signature to a subset of traffic that contains matching packet headers. This type of matching is performed only for the first packet of a connection.
There are pre-defined header conditions for some of the most used
header fields. All of them generally have the format <keyword> <cmp>
<value-list>
, where <keyword>
names the header field; cmp
is
one of ==
, !=
, <
, <=
, >
, >=
; and
<value-list>
is a list of comma-separated values to compare
against. The following keywords are defined:
src-ip
/dst-ip <cmp> <address-list>
[fe80::1]
or [fe80::0]/16
).src-port
/dst-port <cmp> <int-list>
ip-proto <cmp> tcp|udp|icmp|icmp6|ip|ip6
ip
or ip6
is a no-op.For lists of multiple values, they are sequentially compared against
the corresponding header field. If at least one of the comparisons
evaluates to true, the whole header condition matches (exception: with
!=
, the header condition only matches if all values differ).
In addition to these pre-defined header keywords, a general header condition can be defined either as
header <proto>[<offset>:<size>] [& <integer>] <cmp> <value-list>
This compares the value found at the given position of the packet header
with a list of values. offset
defines the position of the value
within the header of the protocol defined by proto
(which can be
ip
, ip6
, tcp
, udp
, icmp
or icmp6
). size
is
either 1, 2, or 4 and specifies the value to have a size of this many
bytes. If the optional & <integer>
is given, the packet’s value is
first masked with the integer before it is compared to the value-list.
cmp
is one of ==
, !=
, <
, <=
, >
, >=
.
value-list
is a list of comma-separated integers similar to those
described above. The integers within the list may be followed by an
additional / mask
where mask
is a value from 0 to 32. This
corresponds to the CIDR notation for netmasks and is translated into a
corresponding bitmask applied to the packet’s value prior to the
comparison (similar to the optional & integer
). IPv6 address values
are not allowed in the value-list, though you can still inspect any 1,
2, or 4 byte section of an IPv6 header using this keyword.
Putting it all together, this is an example condition that is
equivalent to dst-ip == 1.2.3.4/16, 5.6.7.8/24
:
header ip[16:4] == 1.2.3.4/16, 5.6.7.8/24
Note that the analogous example for IPv6 isn’t currently possible since 4 bytes is the max width of a value that can be compared.
Content conditions are defined by regular expressions. We
differentiate two kinds of content conditions: first, the expression
may be declared with the payload
statement, in which case it is
matched against the raw payload of a connection (for reassembled TCP
streams) or of each packet (for ICMP, UDP, and non-reassembled TCP).
Second, it may be prefixed with an analyzer-specific label, in which
case the expression is matched against the data as extracted by the
corresponding analyzer.
A payload
condition has the form:
payload /<regular expression>/
Currently, the following analyzer-specific content conditions are defined (note that the corresponding analyzer has to be activated by loading its policy script):
http-request /<regular expression>/
http
.http-request-header /<regular expression>/
http-request-body /<regular expression>/
http-reply-header /<regular expression>/
http-reply-body /<regular expression>/
ftp /<regular expression>/
finger /<regular expression>/
For example, http-request /.*(etc/(passwd|shadow)/
matches any URI
containing either etc/passwd
or etc/shadow
. To filter on request
types, e.g. GET
, use payload /GET /
.
Note that HTTP pipelining (that is, multiple HTTP transactions in a single TCP connection) has some side effects on signature matches. If multiple conditions are specified within a single signature, this signature matches if all conditions are met by any HTTP transaction (not necessarily always the same!) in a pipelined connection.
To define dependencies between signatures, there are two conditions:
requires-signature [!] <id>
id
matches for the same connection. Using !
negates the
condition: The current signature only matches if id
does not
match for the same connection (using this defers the match
decision until the connection terminates).requires-reverse-signature [!] <id>
requires-signature
, but id
has to match for the
opposite direction of the same connection, compared to the current
signature. This allows to model the notion of requests and
replies.Context conditions pass the match decision on to other components of Bro. They are only evaluated if all other conditions have already matched. The following context conditions are defined:
eval <policy-function>
function
cond(state: signature_state, data: string): bool
. Here,
data
may contain the most recent content chunk available at
the time the signature was matched. If no such chunk is available,
data
will be the empty string. See signature_state
for its definition.payload-size <cmp> <integer>
same-ip
tcp-state <state-list>
state-list
is a comma-separated list of the keywords
established
(the three-way handshake has already been
performed), originator
(the current data is send by the
originator of the connection), and responder
(the current data
is send by the responder of the connection).Actions define what to do if a signature matches. Currently, there are two actions defined:
event <string>
Raises a signature_match
event. The event handler has the
following type:
event signature_match(state: signature_state, msg: string, data: string)
The given string is passed in as msg
, and data is the current
part of the payload that has eventually lead to the signature
match (this may be empty for signatures without content
conditions).
enable <string>
<string>
for the matching
connection ("http"
, "ftp"
, etc.). This is used by Bro’s
dynamic protocol detection to activate analyzers on the fly.The signature framework can also be used to identify MIME types of files
irrespective of the network protocol/connection over which the file is
transferred. A special type of signature can be written for this
purpose and will be used automatically by the Files Framework or by Bro scripts that use the file_magic
built-in function.
File signatures use a single type of content condition in the form of a regular expression:
file-magic /<regular expression>/
This is analogous to the payload
content condition for the network
traffic signature language described above. The difference is that
payload
signatures are applied to payloads of network connections,
but file-magic
can be applied to any arbitrary data, it does not
have to be tied to a network protocol/connection.
Upon matching a chunk of data, file signatures use the following action to get information about that data’s MIME type:
file-mime <string> [, <integer>]
The arguments include the MIME type string associated with the file magic regular expression and an optional “strength” as a signed integer. Since multiple file magic signatures may match against a given chunk of data, the strength value may be used to help choose a “winner”. Higher values are considered stronger.
http
/.*passwd/
scans URLs requested within HTTP sessions. The thing to
keep in mind here is that these conditions only perform any matching
when the corresponding application analyzer is actually active for
a connection. Note that by default, analyzers are not enabled if the
corresponding Bro script has not been loaded. A good way to
double-check whether an analyzer “sees” a connection is checking its
log file for corresponding entries. If you cannot find the
connection in the analyzer’s log, very likely the signature engine
has also not seen any application data.payload
keyword matches on packet
payload only. You cannot use it to match on packet headers; use
the header conditions for that.^
operator. For reassembled TCP connections,
they are anchored at the first byte of the payload stream. For all
other connections, they are anchored at the first payload byte of
each packet. To match at arbitrary positions, you can prefix the
regular expression with .*
, as done in the examples above.\x<hex>
operator. CRs/LFs are not treated specially by the
signature engine and can be matched with \r
and \n
,
respectively. Generally, Bro follows flex’s regular expression
syntax.
See the DPD signatures in base/frameworks/dpd/dpd.sig
for some examples
of fairly complex payload patterns.signature_match
handler might not carry
the full text matched by the regular expression. Bro performs the
matching incrementally as packets come in; when the signature
eventually fires, it can only pass on the most recent chunk of data.The following options control details of Bro’s matching process:
dpd_reassemble_first_packets: bool
(default: T
)dpd_buffer_size
bytes, see below), to facilitate
reliable matching across packet boundaries. If false, only
connections are reassembled for which an application-layer
analyzer gets activated (e.g., by Bro’s dynamic protocol
detection).dpd_match_only_beginning : bool
(default: T
)dpd_buffer_size
. If false, it keeps matching
on subsequent payload as well.dpd_buffer_size: count
(default: 1024
)There was once a script, snort2bro
, that converted Snort
signatures automatically into Bro’s signature syntax. However, in our
experience this didn’t turn out to be a very useful thing to do
because by simply using Snort signatures, one can’t benefit from the
additional capabilities that Bro provides; the approaches of the two
systems are just too different. We therefore stopped maintaining the
snort2bro
script, and there are now many newer Snort options which
it doesn’t support. The script is now no longer part of the Bro
distribution.