One of the easiest ways to customize Bro
is writing a local notice policy. Bro can detect a great
number of potentially interesting situations, and the notice policy
tells which of them the user wants to be escalated into a
alarms. The notice policy can also specify further actions
to be taken, such as paging the security officer. This article gives
an introduction into writing such a notice policy.
Let us start with a little bit of background on Bro's philosophy on
reporting things. Bro ships with a large number of policy scripts
which perform a wide variety of analyses. Most of these scripts
monitor for activity which might be of
interest for the administrator. However, none of these scripts
assesses the importance of what it finds itself. Instead, the
scripts only flag situations as
potentially interesting, leaving it to the local
configuration to define which of them are in fact alarm-worthy. This
decoupling of detection and reporting allows Bro to address the
different needs sites have: the definition of what constitutes an attack
differs quite a bit between environments, and activity deemed malicious
at one place might be fully acceptable at another.
Whenever one of Bro's analysis scripts sees something potentially interesting,
it flags the situation by raising a Notice. A Notice has a
type, which reflects the kind of activity which has
been detected, and it is usually augmented with additional context
about the situation. For example, whenever the HTTP analyzer sees a
suspicious URL being requested (such as /etc/passwd), it raises a
Notice of the type HTTP_SensitiveURI and augments it with
the requested URL itself as well as the involved hosts.
In terms of script code, "raising a Notice" is just a
call to a predefined function called NOTICE.
For example, to raise an HTTP_SensitiveURI such a call could
look like this:
NOTICE([$note=HTTP_SensitiveURI, $conn=connection,
$URL=url, ...])
If one wants to know which types of Notices a Bro script can raise, one
can just grep the script for calls to the NOTICE function.
Once raised, all Notices are processed centrally. By default, all
Notices
are in fact automatically turned into alarms and will
therefore show up in alarm.log. The local site policy can
however change this default behavior, as we describe in the following.
In general, each raised Notice gets mapped to one out of a set of
predefined actions. Such an
action can, e.g., be to send a mail to the administrator or
to simply ignore the Notice. In the current
trunk, the
following actions are defined:
| Action | Description |
| NOTICE_IGNORE | Ignore Notice completely. |
| NOTICE_FILE | File Notice only to notice.log; do not write an entry into alarm.log. |
| NOTICE_ALARM_ALWAYS | Report in alarm.log. |
| NOTICE_EMAIL | Send out a mail and report in alarm.log. |
| NOTICE_PAGE | Page security officer and report in alarm.log. |
NOTICE_ALARM_ALWAYS reflects the default behavior if no
other action is defined for a Notice. All notice actions except
NOTICE_IGNORE also log to notice.log.
We can define which action is taken for a Notice in two ways. The
first is to generally assign an action to all instances of a
particular Notice type; the second provides the flexibility to
filter individual Notice instances independent of their type. We
discuss both in turn.
To generally apply the same action to all instances of a specific
type, we
assign a notice action filter to the type. In the most
simple case such a filter does directly correspond to the intended action,
per the following table:
| Filter Name | Action |
| ignore_notice | NOTICE_IGNORE |
| file_notice | NOTICE_FILE |
| send_email_notice | NOTICE_EMAIL |
| send_page_notice | NOTICE_PAGE |
(As NOTICE_ALARM_ALWAYS is the default action, there is no
corresponding filter).
We map a Notice type to such a filter by adding an
entry to Bro's predefined notice_action_filters table. For example, to just file
all sensitive URIs into notice.log rather than turning them
into alarms, we define:
@load notice-action-filters
redef notice_action_filters += {
[HTTP_SensitiveURI] = file_notice
};
Notice action filters are more powerful than just directly defining an
action. Each filter is in fact a script function which gets the
Notice instance as a parameter and returns the action Bro should take. In
general, these functions can implement arbitrary schemes to settle
on an action, which is why they are called "filters". In addition to
the filters mentioned above (which just return the corresponding
action without further ado),
Bro's default script notice-action-filters.bro also defines the
following ones (and more):
| Filter name | Description |
| tally_notice_type | Count how often each
Notice type occurred. The totals are reported when Bro terminates as
new Notices of the type NoticeTally. The original Notices are just
filed into notice.log. |
| tally_notice_type_and_ignore | Similar to
tally_notice_type but discards original Notices. |
| file_if_remote | Do not alarm if Notice was triggered
by a remote address. |
| notice_alarm_per_orig | Alarm only the
first time we see the Notice type for each source address. |
| notice_alarm_per_orig_tally | Count Notice
types per source address. Totals are reported, by default, every 5 hours as
new NoticeTally Notices. The original Notices are
just filed into notice.log. |
Such filter functions are actually pretty easy to write. Have a look
at the implementations in notice-action-filters.bro if
you need different behavior.
Bro's set notice_policy provides the second way to define an
action to be taken for a Notice. While notice_action_filters
maps all instances of a particular Notice type to the same filter,
notice_policy works on individual Notice instances. Each
entry of notice_policy defines (1) a condition to be matched
against all raised Notices and (2) an action to be taken if the
condition matches.
Here's a simple example which tells Bro to ignore
all Notices of type HTTP_SensitiveURI if the requested URL
indicates that an image was requested (simplified example taken from
policy/notice-policy.bro):
redef notice_policy += {
[$pred(n: notice_info) = {
return n$note == HTTP::HTTP_SensitiveURI &&
n$URL == /.*\.(gif|jpg|png)/;
},
$result = NOTICE_IGNORE]
};
While the syntax might look a bit convoluted at first, it
provides a lot of flexibility by leveraging Bro's match statement.
$pred defines the entry's condition in the form of a
predicate written as a Bro function. The function gets passed the raised
Notice and it returns a boolean indicating whether the entry applies. If
the predicate evaluates to true, Bro takes the action specified by $result. (If
$result is omitted, the default action for a matching entry
is NOTICE_FILE).
The notice_policy set can hold an arbitrary number of such
entries. For each Notice, Bro evaluates the predicates of all of
them. If multiple predicates evaluate to true, it is undefined which
of the matching results is taken. One can however associate a
priority with an entry by adding a field
$priority=<int> to its definition; see
policy/notice-policy.bro for examples. In the
case of multiple matches with different priorities, Bro picks the one
with the highest. If $priority is omitted, as it is in the example above,
the default priority is 1.