Exercise: Building a Profile of Local Network Services

In many networks, the types of network services offered by internal hosts tend to be rather stable over time. If a system suddenly starts running a new service (i.e., accepts connections on a port it did not before), this can be a noteworthy event by itself. In this exercise we will write a Bro script which tracks the services offered by local hosts over time and generates a notice when a new one pops up.

  1. Write a script which for every local host remembers the destination ports of all incoming, fully established connections. Generate a notice whenever a host accepts a connection for the first time on a specific port. Run the script on the trace enterprise-1.trace. Assume that all hosts in 128.3.0.0/16 and 131.243.0.0/16 are internal, and all others are external.

  2. Make the detection a bit more customizable:

    How would you set these options for the traffic in enterprise-1.trace?

  3. Extend the script to keep the collected port information across restarts. Run first with enterprise-1.trace and then with enterprise-2.trace. How many tuples (host,port) has Bro remembered after running on the first trace, how many after running on both?

  4. If run for the first time, the script reports lots of services because it does not have any previous knowledge about the network. Add a learning mode which, when activated, builds the service profile but does not generate any notice.

  5. Extend the script to print a summary of remembered (host, port) tuples when Bro terminates.

Here's a template as a starting point.

Events which might be of use to solve this:

event connection_established(c: connection)
       # Raised whenever Bro sees a fully established connection.
       
event bro_done()
       # Raised just before the Bro process terminates.

Some syntax hints:

- Check for privileged port: 
    if ( p >= 1024/tcp ) ...

- Insert element into set: 
    add services[1.2.3.4, 25/tcp];  

- Check whether element is in set:
    if ( [1.2.3.4, 25/tcp] in services ) ...  

Because the traces contain only headers, add this to suppress warnings about missing payload:
@load weird

redef notice_action_filters += {
    [ContentGap] = ignore_notice
};

redef notice_policy += {
    [$pred(a: notice_info) = { 
        return a$note == WeirdActivity &&
            ("incompletely_captured_fragment" in a$msg ||
             "UDP_datagram_length_mismatch" in a$msg);
    }, 
    $result = NOTICE_IGNORE]
};