In this appendix we give an overview of how the different elements of Bro come together for monitoring Finger traffic. For the event engine, we have a C++ class FingerConn, derived from the general-purpose TCP_Connection class. When Bro encounters a new connection with service port 79, it instantiates a corresponding FingerConn object, instead of a TCP_Connection object as it would for an unrecognized port.
FingerConn redefines the virtual function BuildEndpoints, which is invoked when a connection object is first created:
void FingerConn::BuildEndpoints() { resp = new TCP_Endpoint(this, 0); orig = new TCP_EndpointLine(this, 1, 1, 0); }Here, resp, corresponding to the responder (Finger server) side of the connection, is initialized to an ordinary TCP_Endpoint object, because Bro does not (presently) look inside Finger replies. But orig, the Finger client side, is initialized to a TCP_EndpointLine object, which means Bro will track the contents of that side of the connection, and, furthermore, deliver the contents in a line-oriented fashion to FingerConn's virtual NewLine function:
int FingerConn::NewLine(TCP_Endpoint* /* s */, double /* t */, char* line) { line = skip_whitespace(line); // Check for /W. int is_long = (line[0] == '/' && toupper(line[1]) == 'W'); if ( is_long ) line = skip_whitespace(line+2); val_list* vl = new val_list; vl->append(BuildConnVal()); vl->append(new StringVal(line)); vl->append(new Val(is_long, TYPE_BOOL)); mgr.QueueEvent(finger_request, vl); return 0; }NewLine skips whitespace in the request, scans it for the ``/W'' indicator (which requests verbose Finger output), and moves past it if present. It then creates a val_list object, which holds a list of generic Bro Val objects. The first of these is assigned to a generic connection-identifier value (see below); the second, to a Bro string containing the Finger request, and the third to a bool indicating whether the request was verbose or not. The penultimate line queues a new finger_request event with the corresponding list of values as arguments; finally, return 0 indicates that the FingerConn is all done with the memory associated with line (since new StringVal(line) made a copy of it), so that memory can be reclaimed by the caller.
The connection identifier discussed above is defined in Bro as a ``connection'' record:
type endpoint: record { size: count; state: count; }; type connection: record { id: conn_id; orig: endpoint; resp: endpoint; start_time: time; duration: interval; service: string; # if empty, service not yet determined addl: string; hot: count; # how hot; 0 = don't know or not hot };The id field is a conn_id record, discussed in § 3.1. orig and resp correspond to the connection originator and responder, each a Bro endpoint record consisting of size (the number of bytes transferred by that endpoint so far) and state, the endpoint's TCP state (e.g., SYN sent, established, closed). This latter would be better expressed using an enumerated type (rather than a count), which we may add to Bro in the future.
The start_time field reflects when the connection's first packet was seen, and duration how long the connection has existed. service corresponds to the name of the service, or an empty string if it has not been identified. By convention, addl holds additional information associated with the connection; better than a string here would be some sort of union or generic type, if Bro supported such. Finally, by convention the policy script increments hot whenever it finds something potentially suspicious about the connection.
Here is the corresponding policy script:
global hot_names = { "root", "lp", "uucp" }; global finger_log = open(getenv("BRO_ID") == "" ? "finger.log" : fmt("finger.%s", getenv("BRO_ID"))); event finger_request(c:connection, request: string, full: bool) { if ( byte_len(request) > 80 ) { request = fmt("%s...", sub_bytes(request, 1, 80)); ++c$hot; } if ( request in hot_names ) ++c$hot; local req = request == "" ? "ANY" : fmt("\"%s\"", request); if ( c$addl != "" ) # This is an additional request. req = fmt("(%s)", req); if ( full ) req = fmt("%s (/W)", req); local msg = fmt("%s > %s %s", c$id$orig_h, c$id$resp_h, req); if ( c$hot > 0 ) log fmt("finger: %s", msg); print finger_log, fmt("%.6f %s", c$start_time, msg); c$addl = c$addl == "" ? req : fmt("*%s, %s", c$addl, req); }The global hot_names is a Bro set of string. In the next line, finger_log is initialized to a Bro file, either named ``finger.log'', or, if the BRO_ID environment variable is set, to a name derived from it using the built-in fmt function.
The finger_request event handler follows. It takes three arguments, corresponding to the values added to the val_list above. It first checks whether the request is excessively long, and, if so, truncates it and increments the hot field of the connection's information record. (The Bro built-in functions used here are named in terms of ``bytes'' rather than ``string'' because they make no assumptions about NUL-termination of their arguments; in particular, byte_len returns the length of its argument including a final NUL byte, if present.)
Next, the script checks whether the request corresponds to any of the entries in the hot_names set. If so, it again marks the connection as ``hot.''
We then initialize the local variable req to a quoted version of the request; or, if the request was empty (which in the Finger protocol indicates a request type of ``ANY''), then it is changed to ``ANY''.
The event handler stores the Finger request in the connection record's addl field (see below), so the next line checks to see whether this field already contains a request. If so, then we are seeing multiple requests for a single Finger connection. This is not allowed by the Finger protocol, but that doesn't mean we won't see them! In particular, we might imagine a subterfuge attack in which an attacker queries an innocuous name in their first request, and a sensitive name in their second, and depending on how the finger server is written, it may well respond to both. This script will still catch such use, since it fully processes each request; but it needs to be careful to keep the global state corresponding to the connection (in the addl field) complete. To do so, it marks additional requests by enclosing them in parentheses, and also prepends an asterisk to the entire addl field for each additional request, so that in later visual inspection of the Finger logs these requests immediately stand out.
The msg local variable holds the basic description of the Finger request. The fmt function knows to format the IP addresses c$id$orig_h and c$id$resp_h as ``dotted quads.''
Next, if the connection has been marked as ``hot'' (either just previously, or perhaps by a completely different event handler), then the script generates a real-time notification. In any case, it also records the request to the finger_log file. Finally, it updates the addl field to reflect the request (and to flag multiple requests, as discussed above).
Entries in the log file look like:
880988813.752829 171.64.15.68 > 128.3.253.104 "feng" 880991121.364126 131.243.168.28 > 130.132.143.23 "anlin" 880997120.932007 192.84.144.6 > 128.3.32.16 ALL 881000846.603872 128.3.9.45 > 146.165.7.14 ALL (/W) 881001601.958411 152.66.83.11 > 128.3.13.76 "davfor"(though without the lines split after the ``
>
'').
The real-time notifications look quite similar, with the keyword ``finger:'' added to avoid ambiguity with other types of real-time notification.