Monday, November 03, 2008

Detecting outgoing connections from sensitive networks with Bro

As I mentioned in my last post, I've been playing with the Bro IDS. I wanted to take a stab at creating my own policy, just to see what it's like to program for Bro. It turned out to be surprisingly easy.

I started with the following policy statement: There are certain hosts and subnets on my site which should never initiate connections to the Internet. Given that, I want to be notified whenever these forbidden connections happen. To accomplish this, I created restricted-outgoing.bro.

To use this sample, copy into your $BRO_DIR/site directory, and add "@load restricted-outgoing" to your startup policy (the mybro.bro script if you're following my previous example).

## Detect unexpected outgoing traffic from restricted subnets
@load restricted-outgoing

Now the code is loaded and running, but it must be configured according to your site's individual list of restricted hosts and subnets. Following the above lines, you can add something like:

redef RestrictedOutgoing::restricted_outgoing_networks = {, # restricted subnet, # restricted subnet, # individual host

If you run Bro now, you should start seeing lines like the following in your $BRO_DIR/logs/alarms.log file:

1225743661.858791 UnexpectedOutgoingUDPConnection
x.x.x.x/netbios-ns > y.y.y.y/netbios-ns : Restricted
Outgoing UDP Connection

1225743661.858791 UnexpectedOutgoingTCPConnection
x.x.x.x/netbios-ns > y.y.y.y/netbios-ns : Restricted
Outgoing TCP Connection

Similar entries will also show up in your $BRO_DIR/logs/restricted-outgoing.file.

This script considers a connection to be a tuple composed of the following values: (src_ip, dst_ip, dst_port). When it alerts, that connection is placed on a temporary ignore list to suppress further alerts, and a per-connection timer starts counting down. Additional identical connections reset the timer. When the counter finally reaches 0, the connection is removed from the ignore list, so you'll receive another alert next time it happens. The default value for this timer is 60 seconds, but you can change it by using the following code:

redef RestrictedOutgoing::restricted_connection_timeout = 120 secs;

Even though your policy may state that outgoing connections are not allowed from these sources, it may be the case that you have certain exceptions. For example, Microsoft Update servers are useful for Windows systems. There are three ways to create exceptions for this module:

First, you can define a list of hosts that any of the restricted nets is allowed to access:

redef RestrictedOutgoing::allowed_outgoing_dsts = {, # Akamai, usually updates

Second, you can list services which particular subnets or hosts are allowed to contact:

redef RestrictedOutgoing::allowed_outgoing_network_service_pairs = {
[, 25/tcp], # SMTP server
[, 80/tcp], # Web proxy
[, 53/udp], # DNS server
[, 123/udp], # NTP

Finally, you can list specific pairs of hosts which are allowed to communicate:

redef RestrictedOutgoing::allowed_outgoing_dst_pairs = {

Note that this is the only one of the three options that allow you specify individual IPs without CIDR block notation, or to use hostnames. The hostnames are especially useful, as Bro automatically knows about all the different IPs that the hostnames resolve to, so the "" above would match any of the IPs returned by DNS.

Overall, I found the Bro language pretty easy to learn, and very well suited for the types of things a security analyst typically wants to look for. I was able to bang out a rough draft of this policy script on my second day working with Bro, and I refined it a bit more on the third day. Of course, I'm sure an actual Bro expert could tell me all sorts of things I did wrong. If you're that Bro expert, please leave a comment below!

Update 2008-11-06 14:45 I have uploaded a new version of the code with some additional functionality. Check the comments below for details. And thanks to Seth for his help!


Seth Hall said...

Hi David, thanks for writing this script. I've found that some of the small scripts like will tend to be some of the most incredibly useful scripts you use.

To make the script a little nicer, you could change it to only throw a single notice: "UnexpectedOutgoingConnection". You could reduce the check_restricted_outgoing_tcp and check_restricted_outgoing_udp functions into just check_restricted_outgoing since the code for those two is almost completely duplicate and the protocol information would still be contained when you throw the notice because you're attaching the actual connection record. If you're still interesting in including the protocol type in the message of the notice, you can get the protocol type with the
get_conn_transport_proto function (e.g. get_conn_transport_proto(c$id) )

It looks like you could probably remove the "const CONN_ATTEMPTED", etc. definitions from the beginning too, those don't seem to be used for anything. Other than those little things, I don't see anything else worth pointing out.

It's a nice script, I might start down this path too. In my environment it's somewhat difficult to determine what hosts should and shouldn't be doing certain activities but that's no reason not to give it a try.

DavidJBianco said...

Seth, thanks for the great tips! I took all your suggestions, and have uploaded a new, more concise, version. As a bonus, I hooked into just a single event this time, the new_connection event, so the script now works for ICMP and anything else Bro detects.