zeek/policy/http.bro
Gregor Maier b8ab0ebc22 Remvoing expire timer from http_sessions.
The expire timeout for the http_sessions table is unnecessary and it
actually breaks http session semantics for long-lived sessions. The
connection_state_remove() event can take care of cleaning up unanswered
sessions.

If a HTTP transfer exceeds the expire timer, then once the expire timer
fires we get an "unanswered" HTTP request in http.log and once the
reply is done (http_reply_done event), it fails to locate the associated
request (because it expired) and thus results in an "unsolicited" HTTP
reply being logged (althoug they should be one http session).

There was a comment in the expire_function mentioning that without the
expire timer some requests don't show up with the test-suite. However,
after checking back with Robin, I could not reproduce this behavior.
(Actually there's one fewer request in the output without the
expire-timer, but this can be explained by the above observation, so
this is not an error but the way it should be).

This patch results in changes to test-suite output:
  * Timestamps for unanswered HTTP replies differ for unanswered request
    in the "short" test.
  * Medium testcase (note: lines are sorted, they are not in the order)::

	-902189670.828700 <unknown request> (0 "" [40880 (interrupted)])
	-902189670.828700 GET /1998/b142.ps <no reply>
	-902189670.828700 start <<IP>>:<<port>> <<IP>>:80
	+902189670.828700 GET /1998/b142.ps (200 "OK" [40880 (interrupted)] <<a.host.name>>)
2010-12-11 10:51:37 -08:00

202 lines
4.8 KiB
Text

# $Id: http.bro 6726 2009-06-07 22:09:55Z vern $
@load notice
@load site
@load conn-id
module HTTP;
export {
redef enum Notice += {
HTTP_SensitiveURI, # sensitive URI in GET/POST/HEAD
};
}
# DPM configuration.
global http_ports = {
80/tcp, 81/tcp, 631/tcp, 1080/tcp, 3138/tcp,
8000/tcp, 8080/tcp, 8888/tcp,
};
redef dpd_config += { [ANALYZER_HTTP] = [$ports = http_ports] };
redef dpd_config += { [ANALYZER_HTTP_BINPAC] = [$ports = http_ports] };
# HTTP processing options.
export {
const process_HTTP_replies = F &redef;
const process_HTTP_data = F &redef;
const include_HTTP_abstract = F &redef;
const log_HTTP_data = F &redef;
}
type http_pending_request: record {
method: string;
URI: string;
log_it: bool;
# Whether we determined it's an attempted passwd file fetch.
passwd_req: bool;
};
# Eventually we will combine http_pending_request and http_message.
type http_message: record {
initiated: bool;
code: count; # for HTTP reply message
reason: string; # for HTTP reply message
entity_level: count; # depth of enclosing MIME entities
data_length: count; # actual length of data delivered
content_length: string; # length specified in CONTENT-LENGTH header
header_slot: count; # rewrite slot at the end of headers
abstract: string; # data abstract
skip_abstract: bool; # to skip abstract for certain content types
host: string; # host indicated in Host header
};
type http_pending_request_stream: record {
# Number of first pending request.
first_pending_request: count &default = 0;
# Total number of pending requests.
num_pending_requests: count &default = 0;
# Indexed from [first_pending_request ..
# (first_pending_request + num_pending_requests - 1)]
requests: table[count] of http_pending_request;
next_request: http_message; # the on-going request
next_reply: http_message; # the on-going reply
# len_next_reply: count; # 0 means unspecified
# len_next_request: count;
id: string; # repeated from http_session_info, for convenience
};
type http_session_info: record {
id: string;
request_stream: http_pending_request_stream;
};
const http_log = open_log_file("http") &redef;
# Indexed by conn_id.
global http_sessions: table[conn_id] of http_session_info;
global http_session_id = 0;
function init_http_message(msg: http_message)
{
msg$initiated = F;
msg$code = 0;
msg$reason = "";
msg$entity_level = 0;
msg$data_length = 0;
msg$content_length = "";
msg$header_slot = 0;
msg$abstract = "";
msg$skip_abstract = F;
msg$host = "";
}
function new_http_message(): http_message
{
local msg: http_message;
init_http_message(msg);
return msg;
}
function new_http_session(c: connection): http_session_info
{
local session = c$id;
local new_id = ++http_session_id;
local info: http_session_info;
info$id = fmt("%%%s", prefixed_id(new_id));
local rs: http_pending_request_stream;
rs$first_pending_request = 1;
rs$num_pending_requests = 0;
rs$id = info$id;
rs$next_request = new_http_message();
rs$next_reply = new_http_message();
rs$requests = table();
info$request_stream = rs;
http_sessions[session] = info;
print http_log, fmt("%.6f %s start %s:%d > %s:%d", network_time(),
info$id, c$id$orig_h,
c$id$orig_p, c$id$resp_h, c$id$resp_p);
return info;
}
function lookup_http_session(c: connection): http_session_info
{
local s: http_session_info;
local id = c$id;
s = id in http_sessions ? http_sessions[id] : new_http_session(c);
append_addl(c, s$id);
return s;
}
function lookup_http_request_stream(c: connection): http_pending_request_stream
{
local s = lookup_http_session(c);
return s$request_stream;
}
function get_http_message(s: http_pending_request_stream, is_orig: bool): http_message
{
return is_orig ? s$next_request : s$next_reply;
}
function finish_stream(session: conn_id, id: string,
rs: http_pending_request_stream)
{
### We really want to do this in sequential order, not table order.
for ( i in rs$requests )
{
local req = rs$requests[i];
if ( req$log_it )
NOTICE([$note=HTTP_SensitiveURI,
$src=session$orig_h, $dst=session$resp_h,
$URL=req$URI,
$method=req$method,
$msg=fmt("%s:%d -> %s:%d %s: <no reply>",
session$orig_h, session$orig_p,
session$resp_h, session$resp_p, id)]);
local msg = fmt("%s %s <no reply>", req$method, req$URI);
print http_log, fmt("%.6f %s %s", network_time(), rs$id, msg);
}
}
event connection_state_remove(c: connection)
{
local id = c$id;
if ( id !in http_sessions )
return;
local s = http_sessions[id];
finish_stream(id, s$id, s$request_stream);
delete http_sessions[c$id];
}
# event http_stats(c: connection, stats: http_stats_rec)
# {
# if ( stats$num_requests == 0 && stats$num_replies == 0 )
# return;
#
# c$addl = fmt("%s (%d v%.1f v%.1f)", c$addl, stats$num_requests, stats$request_version, stats$reply_version);
# }