mirror of
https://github.com/zeek/zeek.git
synced 2025-10-17 05:58:20 +00:00
Merge remote-tracking branch 'origin/topic/seth/faf-updates'
* origin/topic/seth/faf-updates: (27 commits) Undoing the FTP tests I updated earlier. Update the last two btest FAF tests. File analysis fixes and test updates. Fix a bug with getting analyzer tags. A few test updates. Some tests work now (at least they all don't fail anymore!) Forgot a file. Added protocol description functions that provide a super compressed log representation. Fix a bug where orig file information in http wasn't working right. Added mime types to http.log Clean up queued but unused file_over_new_connections event args. Add jar files to the default MHR lookups. Adding CAB files for MHR checking. Improve malware hash registry script. Fix a small issue with finding smtp entities. Added support for files to the notice framework. Make the custom libmagic database a git submodule. Add an is_orig parameter to file_over_new_connection event. Make magic for emitting application/msword mime type less strict. Disable more libmagic builtin checks that override the magic database. ... Conflicts: doc/scripts/DocSourcesList.cmake scripts/base/init-bare.bro scripts/test-all-policy.bro testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log
This commit is contained in:
commit
984e9793db
196 changed files with 1548 additions and 5033 deletions
|
@ -1,7 +1,7 @@
|
|||
@load ./utils-commands
|
||||
@load ./main
|
||||
@load ./file-analysis
|
||||
@load ./file-extract
|
||||
@load ./utils
|
||||
@load ./files
|
||||
@load ./gridftp
|
||||
|
||||
@load-sigs ./dpd.sig
|
||||
@load-sigs ./dpd.sig
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
@load ./main
|
||||
@load base/utils/conn-ids
|
||||
@load base/frameworks/file-analysis/main
|
||||
|
||||
module FTP;
|
||||
|
||||
export {
|
||||
## Default file handle provider for FTP.
|
||||
global get_file_handle: function(c: connection, is_orig: bool): string;
|
||||
}
|
||||
|
||||
function get_handle_string(c: connection): string
|
||||
{
|
||||
return cat(Analyzer::ANALYZER_FTP_DATA, " ", c$start_time, " ", id_string(c$id));
|
||||
}
|
||||
|
||||
function get_file_handle(c: connection, is_orig: bool): string
|
||||
{
|
||||
if ( [c$id$resp_h, c$id$resp_p] !in ftp_data_expected ) return "";
|
||||
|
||||
local info: FTP::Info = ftp_data_expected[c$id$resp_h, c$id$resp_p];
|
||||
|
||||
if ( info$passive )
|
||||
# FTP client initiates data channel.
|
||||
if ( is_orig )
|
||||
# Don't care about FTP client data.
|
||||
return "";
|
||||
else
|
||||
# Do care about FTP server data.
|
||||
return get_handle_string(c);
|
||||
else
|
||||
# FTP server initiates dta channel.
|
||||
if ( is_orig )
|
||||
# Do care about FTP server data.
|
||||
return get_handle_string(c);
|
||||
else
|
||||
# Don't care about FTP client data.
|
||||
return "";
|
||||
}
|
||||
|
||||
module GLOBAL;
|
||||
|
||||
event get_file_handle(tag: Analyzer::Tag, c: connection, is_orig: bool)
|
||||
&priority=5
|
||||
{
|
||||
if ( tag != Analyzer::ANALYZER_FTP_DATA ) return;
|
||||
set_file_handle(FTP::get_file_handle(c, is_orig));
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
##! File extraction support for FTP.
|
||||
|
||||
@load ./main
|
||||
@load base/utils/files
|
||||
|
||||
module FTP;
|
||||
|
||||
export {
|
||||
## Pattern of file mime types to extract from FTP transfers.
|
||||
const extract_file_types = /NO_DEFAULT/ &redef;
|
||||
|
||||
## The on-disk prefix for files to be extracted from FTP-data transfers.
|
||||
const extraction_prefix = "ftp-item" &redef;
|
||||
}
|
||||
|
||||
redef record Info += {
|
||||
## On disk file where it was extracted to.
|
||||
extraction_file: string &log &optional;
|
||||
|
||||
## Indicates if the current command/response pair should attempt to
|
||||
## extract the file if a file was transferred.
|
||||
extract_file: bool &default=F;
|
||||
};
|
||||
|
||||
function get_extraction_name(f: fa_file): string
|
||||
{
|
||||
local r = fmt("%s-%s.dat", extraction_prefix, f$id);
|
||||
return r;
|
||||
}
|
||||
|
||||
event file_new(f: fa_file) &priority=5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "FTP_DATA" ) return;
|
||||
|
||||
if ( f?$mime_type && extract_file_types in f$mime_type )
|
||||
{
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_EXTRACT,
|
||||
$extract_filename=get_extraction_name(f)]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( [cid$resp_h, cid$resp_p] !in ftp_data_expected ) next;
|
||||
|
||||
local s = ftp_data_expected[cid$resp_h, cid$resp_p];
|
||||
|
||||
if ( ! s$extract_file ) next;
|
||||
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_EXTRACT,
|
||||
$extract_filename=get_extraction_name(f)]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event file_state_remove(f: fa_file) &priority=4
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "FTP_DATA" ) return;
|
||||
if ( ! f?$info ) return;
|
||||
|
||||
for ( filename in f$info$extracted_files )
|
||||
{
|
||||
local s: FTP::Info;
|
||||
s$ts = network_time();
|
||||
s$tags = set();
|
||||
s$user = "<ftp-data>";
|
||||
s$extraction_file = filename;
|
||||
|
||||
if ( f?$conns )
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
s$uid = f$conns[cid]$uid;
|
||||
s$id = cid;
|
||||
}
|
||||
|
||||
Log::write(FTP::LOG, s);
|
||||
}
|
||||
}
|
||||
|
||||
event log_ftp(rec: Info) &priority=-10
|
||||
{
|
||||
delete rec$extraction_file;
|
||||
delete rec$extract_file;
|
||||
}
|
60
scripts/base/protocols/ftp/files.bro
Normal file
60
scripts/base/protocols/ftp/files.bro
Normal file
|
@ -0,0 +1,60 @@
|
|||
@load ./main
|
||||
@load ./utils
|
||||
@load base/utils/conn-ids
|
||||
@load base/frameworks/files
|
||||
|
||||
module FTP;
|
||||
|
||||
export {
|
||||
redef record Info += {
|
||||
## File unique ID.
|
||||
fuid: string &optional &log;
|
||||
};
|
||||
|
||||
## Default file handle provider for FTP.
|
||||
global get_file_handle: function(c: connection, is_orig: bool): string;
|
||||
|
||||
## Describe the file being transferred.
|
||||
global describe_file: function(f: fa_file): string;
|
||||
}
|
||||
|
||||
function get_file_handle(c: connection, is_orig: bool): string
|
||||
{
|
||||
if ( [c$id$resp_h, c$id$resp_p] !in ftp_data_expected )
|
||||
return "";
|
||||
|
||||
return cat(Analyzer::ANALYZER_FTP_DATA, c$start_time, c$id, is_orig);
|
||||
}
|
||||
|
||||
function describe_file(f: fa_file): string
|
||||
{
|
||||
# This shouldn't be needed, but just in case...
|
||||
if ( f$source != "FTP" )
|
||||
return "";
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
if ( f$conns[cid]?$ftp )
|
||||
return FTP::describe(f$conns[cid]$ftp);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Files::register_protocol(Analyzer::ANALYZER_FTP_DATA,
|
||||
[$get_file_handle = FTP::get_file_handle,
|
||||
$describe = FTP::describe_file]);
|
||||
}
|
||||
|
||||
|
||||
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( [c$id$resp_h, c$id$resp_p] !in ftp_data_expected )
|
||||
return;
|
||||
|
||||
local ftp = ftp_data_expected[c$id$resp_h, c$id$resp_p];
|
||||
ftp$fuid = f$id;
|
||||
if ( f?$mime_type )
|
||||
ftp$mime_type = f$mime_type;
|
||||
}
|
|
@ -63,8 +63,6 @@ export {
|
|||
reply_code: count &log &optional;
|
||||
## Reply message from the server in response to the command.
|
||||
reply_msg: string &log &optional;
|
||||
## Arbitrary tags that may indicate a particular attribute of this command.
|
||||
tags: set[string] &log;
|
||||
|
||||
## Expected FTP data channel.
|
||||
data_channel: ExpectedDataChannel &log &optional;
|
||||
|
@ -104,6 +102,8 @@ export {
|
|||
global log_ftp: event(rec: Info);
|
||||
}
|
||||
|
||||
@load ./utils
|
||||
|
||||
# Add the state tracking information variable to the connection record
|
||||
redef record connection += {
|
||||
ftp: Info &optional;
|
||||
|
@ -171,37 +171,26 @@ function set_ftp_session(c: connection)
|
|||
|
||||
function ftp_message(s: Info)
|
||||
{
|
||||
# If it either has a tag associated with it (something detected)
|
||||
# or it's a deliberately logged command.
|
||||
if ( |s$tags| > 0 || (s?$cmdarg && s$cmdarg$cmd in logged_commands) )
|
||||
s$ts=s$cmdarg$ts;
|
||||
s$command=s$cmdarg$cmd;
|
||||
|
||||
s$arg = s$cmdarg$arg;
|
||||
if ( s$cmdarg$cmd in file_cmds )
|
||||
s$arg = build_url_ftp(s);
|
||||
|
||||
if ( s$arg == "" )
|
||||
delete s$arg;
|
||||
|
||||
if ( s?$password &&
|
||||
! s$capture_password &&
|
||||
to_lower(s$user) !in guest_ids )
|
||||
{
|
||||
if ( s?$password &&
|
||||
! s$capture_password &&
|
||||
to_lower(s$user) !in guest_ids )
|
||||
{
|
||||
s$password = "<hidden>";
|
||||
}
|
||||
|
||||
local arg = s$cmdarg$arg;
|
||||
if ( s$cmdarg$cmd in file_cmds )
|
||||
{
|
||||
local comp_path = build_path_compressed(s$cwd, arg);
|
||||
if ( comp_path[0] != "/" )
|
||||
comp_path = cat("/", comp_path);
|
||||
|
||||
arg = fmt("ftp://%s%s", addr_to_uri(s$id$resp_h), comp_path);
|
||||
}
|
||||
|
||||
s$ts=s$cmdarg$ts;
|
||||
s$command=s$cmdarg$cmd;
|
||||
if ( arg == "" )
|
||||
delete s$arg;
|
||||
else
|
||||
s$arg=arg;
|
||||
|
||||
Log::write(FTP::LOG, s);
|
||||
s$password = "<hidden>";
|
||||
}
|
||||
|
||||
if ( s?$cmdarg && s$command in logged_commands)
|
||||
Log::write(FTP::LOG, s);
|
||||
|
||||
# The MIME and file_size fields are specific to file transfer commands
|
||||
# and may not be used in all commands so they need reset to "blank"
|
||||
# values after logging.
|
||||
|
@ -209,8 +198,6 @@ function ftp_message(s: Info)
|
|||
delete s$file_size;
|
||||
# Same with data channel.
|
||||
delete s$data_channel;
|
||||
# Tags are cleared everytime too.
|
||||
s$tags = set();
|
||||
}
|
||||
|
||||
function add_expected_data_channel(s: Info, chan: ExpectedDataChannel)
|
||||
|
@ -218,8 +205,9 @@ function add_expected_data_channel(s: Info, chan: ExpectedDataChannel)
|
|||
s$passive = chan$passive;
|
||||
s$data_channel = chan;
|
||||
ftp_data_expected[chan$resp_h, chan$resp_p] = s;
|
||||
Analyzer::schedule_analyzer(chan$orig_h, chan$resp_h, chan$resp_p, Analyzer::ANALYZER_FTP_DATA,
|
||||
5mins);
|
||||
Analyzer::schedule_analyzer(chan$orig_h, chan$resp_h, chan$resp_p,
|
||||
Analyzer::ANALYZER_FTP_DATA,
|
||||
5mins);
|
||||
}
|
||||
|
||||
event ftp_request(c: connection, command: string, arg: string) &priority=5
|
||||
|
|
47
scripts/base/protocols/ftp/utils.bro
Normal file
47
scripts/base/protocols/ftp/utils.bro
Normal file
|
@ -0,0 +1,47 @@
|
|||
##! Utilities specific for FTP processing.
|
||||
|
||||
@load ./main
|
||||
@load base/utils/addrs
|
||||
|
||||
module FTP;
|
||||
|
||||
export {
|
||||
## Creates a URL from an :bro:type:`FTP::Info` record.
|
||||
##
|
||||
## rec: An :bro:type:`FTP::Info` record.
|
||||
##
|
||||
## Returns: A URL, not prefixed by "ftp://".
|
||||
global build_url: function(rec: Info): string;
|
||||
|
||||
## Creates a URL from an :bro:type:`FTP::Info` record.
|
||||
##
|
||||
## rec: An :bro:type:`FTP::Info` record.
|
||||
##
|
||||
## Returns: A URL prefixed with "ftp://".
|
||||
global build_url_ftp: function(rec: Info): string;
|
||||
|
||||
## Create an extremely shortened representation of a log line.
|
||||
global describe: function(rec: Info): string;
|
||||
}
|
||||
|
||||
function build_url(rec: Info): string
|
||||
{
|
||||
if ( !rec?$arg )
|
||||
return "";
|
||||
|
||||
local comp_path = build_path_compressed(rec$cwd, rec$arg);
|
||||
if ( comp_path[0] != "/" )
|
||||
comp_path = cat("/", comp_path);
|
||||
|
||||
return fmt("%s%s", addr_to_uri(rec$id$resp_h), comp_path);
|
||||
}
|
||||
|
||||
function build_url_ftp(rec: Info): string
|
||||
{
|
||||
return fmt("ftp://%s", build_url(rec));
|
||||
}
|
||||
|
||||
function describe(rec: Info): string
|
||||
{
|
||||
return build_url_ftp(rec);
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
@load ./main
|
||||
@load ./entities
|
||||
@load ./utils
|
||||
@load ./file-analysis
|
||||
@load ./file-ident
|
||||
@load ./file-hash
|
||||
@load ./file-extract
|
||||
@load ./files
|
||||
|
||||
@load-sigs ./dpd.sig
|
109
scripts/base/protocols/http/entities.bro
Normal file
109
scripts/base/protocols/http/entities.bro
Normal file
|
@ -0,0 +1,109 @@
|
|||
##! Analysis and logging for MIME entities found in HTTP sessions.
|
||||
|
||||
@load base/frameworks/files
|
||||
@load base/utils/strings
|
||||
@load base/utils/files
|
||||
@load ./main
|
||||
|
||||
module HTTP;
|
||||
|
||||
export {
|
||||
type Entity: record {
|
||||
## Filename for the entity if discovered from a header.
|
||||
filename: string &optional;
|
||||
};
|
||||
|
||||
redef record Info += {
|
||||
## An ordered vector of file unique IDs.
|
||||
orig_fuids: vector of string &log &optional;
|
||||
|
||||
## An ordered vector of mime types.
|
||||
orig_mime_types: vector of string &log &optional;
|
||||
|
||||
## An ordered vector of file unique IDs.
|
||||
resp_fuids: vector of string &log &optional;
|
||||
|
||||
## An ordered vector of mime types.
|
||||
resp_mime_types: vector of string &log &optional;
|
||||
|
||||
## The current entity.
|
||||
current_entity: Entity &optional;
|
||||
## Current number of MIME entities in the HTTP request message body.
|
||||
orig_mime_depth: count &default=0;
|
||||
## Current number of MIME entities in the HTTP response message body.
|
||||
resp_mime_depth: count &default=0;
|
||||
};
|
||||
}
|
||||
|
||||
event http_begin_entity(c: connection, is_orig: bool) &priority=10
|
||||
{
|
||||
set_state(c, F, is_orig);
|
||||
|
||||
if ( is_orig )
|
||||
++c$http$orig_mime_depth;
|
||||
else
|
||||
++c$http$resp_mime_depth;
|
||||
|
||||
c$http$current_entity = Entity();
|
||||
}
|
||||
|
||||
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=3
|
||||
{
|
||||
if ( name == "CONTENT-DISPOSITION" &&
|
||||
/[fF][iI][lL][eE][nN][aA][mM][eE]/ in value )
|
||||
{
|
||||
c$http$current_entity$filename = extract_filename_from_content_disposition(value);
|
||||
}
|
||||
else if ( name == "CONTENT-TYPE" &&
|
||||
/[nN][aA][mM][eE][:blank:]*=/ in value )
|
||||
{
|
||||
c$http$current_entity$filename = extract_filename_from_content_disposition(value);
|
||||
}
|
||||
}
|
||||
|
||||
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( f$source == "HTTP" && c?$http )
|
||||
{
|
||||
if ( c$http?$current_entity && c$http$current_entity?$filename )
|
||||
f$info$filename = c$http$current_entity$filename;
|
||||
|
||||
if ( f$is_orig )
|
||||
{
|
||||
if ( ! c$http?$orig_mime_types )
|
||||
c$http$orig_fuids = string_vec(f$id);
|
||||
else
|
||||
c$http$orig_fuids[|c$http$orig_fuids|] = f$id;
|
||||
|
||||
if ( f?$mime_type )
|
||||
{
|
||||
if ( ! c$http?$orig_mime_types )
|
||||
c$http$orig_mime_types = string_vec(f$mime_type);
|
||||
else
|
||||
c$http$orig_mime_types[|c$http$orig_mime_types|] = f$mime_type;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( ! c$http?$resp_mime_types )
|
||||
c$http$resp_fuids = string_vec(f$id);
|
||||
else
|
||||
c$http$resp_fuids[|c$http$resp_fuids|] = f$id;
|
||||
|
||||
if ( f?$mime_type )
|
||||
{
|
||||
if ( ! c$http?$resp_mime_types )
|
||||
c$http$resp_mime_types = string_vec(f$mime_type);
|
||||
else
|
||||
c$http$resp_mime_types[|c$http$resp_mime_types|] = f$mime_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
event http_end_entity(c: connection, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( c?$http && c$http?$current_entity )
|
||||
delete c$http$current_entity;
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
@load ./main
|
||||
@load ./utils
|
||||
@load base/utils/conn-ids
|
||||
@load base/frameworks/file-analysis/main
|
||||
|
||||
module HTTP;
|
||||
|
||||
export {
|
||||
redef record HTTP::Info += {
|
||||
## Number of MIME entities in the HTTP request message body so far.
|
||||
request_mime_level: count &default=0;
|
||||
## Number of MIME entities in the HTTP response message body so far.
|
||||
response_mime_level: count &default=0;
|
||||
};
|
||||
|
||||
## Default file handle provider for HTTP.
|
||||
global get_file_handle: function(c: connection, is_orig: bool): string;
|
||||
}
|
||||
|
||||
event http_begin_entity(c: connection, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( ! c?$http )
|
||||
return;
|
||||
|
||||
if ( is_orig )
|
||||
++c$http$request_mime_level;
|
||||
else
|
||||
++c$http$response_mime_level;
|
||||
}
|
||||
|
||||
function get_file_handle(c: connection, is_orig: bool): string
|
||||
{
|
||||
if ( ! c?$http ) return "";
|
||||
|
||||
local mime_level: count =
|
||||
is_orig ? c$http$request_mime_level : c$http$response_mime_level;
|
||||
local mime_level_str: string = mime_level > 1 ? cat(mime_level) : "";
|
||||
|
||||
if ( c$http$range_request )
|
||||
return cat(Analyzer::ANALYZER_HTTP, " ", is_orig, " ", c$id$orig_h, " ",
|
||||
build_url(c$http));
|
||||
|
||||
return cat(Analyzer::ANALYZER_HTTP, " ", c$start_time, " ", is_orig, " ",
|
||||
c$http$trans_depth, mime_level_str, " ", id_string(c$id));
|
||||
}
|
||||
|
||||
module GLOBAL;
|
||||
|
||||
event get_file_handle(tag: Analyzer::Tag, c: connection, is_orig: bool)
|
||||
&priority=5
|
||||
{
|
||||
if ( tag != Analyzer::ANALYZER_HTTP ) return;
|
||||
set_file_handle(HTTP::get_file_handle(c, is_orig));
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
##! Extracts the items from HTTP traffic, one per file. At this time only
|
||||
##! the message body from the server can be extracted with this script.
|
||||
|
||||
@load ./main
|
||||
@load ./file-analysis
|
||||
|
||||
module HTTP;
|
||||
|
||||
export {
|
||||
## Pattern of file mime types to extract from HTTP response entity bodies.
|
||||
const extract_file_types = /NO_DEFAULT/ &redef;
|
||||
|
||||
## The on-disk prefix for files to be extracted from HTTP entity bodies.
|
||||
const extraction_prefix = "http-item" &redef;
|
||||
|
||||
redef record Info += {
|
||||
## On-disk location where files in request body were extracted.
|
||||
extracted_request_files: vector of string &log &optional;
|
||||
|
||||
## On-disk location where files in response body were extracted.
|
||||
extracted_response_files: vector of string &log &optional;
|
||||
|
||||
## Indicates if the response body is to be extracted or not. Must be
|
||||
## set before or by the first :bro:see:`file_new` for the file content.
|
||||
extract_file: bool &default=F;
|
||||
};
|
||||
}
|
||||
|
||||
function get_extraction_name(f: fa_file): string
|
||||
{
|
||||
local r = fmt("%s-%s.dat", extraction_prefix, f$id);
|
||||
return r;
|
||||
}
|
||||
|
||||
function add_extraction_file(c: connection, is_orig: bool, fn: string)
|
||||
{
|
||||
if ( is_orig )
|
||||
{
|
||||
if ( ! c$http?$extracted_request_files )
|
||||
c$http$extracted_request_files = vector();
|
||||
c$http$extracted_request_files[|c$http$extracted_request_files|] = fn;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( ! c$http?$extracted_response_files )
|
||||
c$http$extracted_response_files = vector();
|
||||
c$http$extracted_response_files[|c$http$extracted_response_files|] = fn;
|
||||
}
|
||||
}
|
||||
|
||||
event file_new(f: fa_file) &priority=5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "HTTP" ) return;
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
local fname: string;
|
||||
local c: connection;
|
||||
|
||||
if ( f?$mime_type && extract_file_types in f$mime_type )
|
||||
{
|
||||
fname = get_extraction_name(f);
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_EXTRACT,
|
||||
$extract_filename=fname]);
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
c = f$conns[cid];
|
||||
if ( ! c?$http ) next;
|
||||
add_extraction_file(c, f$is_orig, fname);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
local extracting: bool = F;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
c = f$conns[cid];
|
||||
|
||||
if ( ! c?$http ) next;
|
||||
|
||||
if ( ! c$http$extract_file ) next;
|
||||
|
||||
fname = get_extraction_name(f);
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_EXTRACT,
|
||||
$extract_filename=fname]);
|
||||
extracting = T;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( extracting )
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
c = f$conns[cid];
|
||||
if ( ! c?$http ) next;
|
||||
add_extraction_file(c, f$is_orig, fname);
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
##! Calculate hashes for HTTP body transfers.
|
||||
|
||||
@load ./main
|
||||
@load ./file-analysis
|
||||
|
||||
module HTTP;
|
||||
|
||||
export {
|
||||
redef record Info += {
|
||||
## MD5 sum for a file transferred over HTTP calculated from the
|
||||
## response body.
|
||||
md5: string &log &optional;
|
||||
|
||||
## This value can be set per-transfer to determine per request
|
||||
## if a file should have an MD5 sum generated. It must be
|
||||
## set to T at the time of or before the first chunk of body data.
|
||||
calc_md5: bool &default=F;
|
||||
};
|
||||
|
||||
## Generate MD5 sums for these filetypes.
|
||||
const generate_md5 = /application\/x-dosexec/ # Windows and DOS executables
|
||||
| /application\/x-executable/ # *NIX executable binary
|
||||
&redef;
|
||||
}
|
||||
|
||||
event file_new(f: fa_file) &priority=5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "HTTP" ) return;
|
||||
|
||||
if ( f?$mime_type && generate_md5 in f$mime_type )
|
||||
{
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_MD5]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( ! c?$http ) next;
|
||||
|
||||
if ( ! c$http$calc_md5 ) next;
|
||||
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_MD5]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event file_state_remove(f: fa_file) &priority=4
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "HTTP" ) return;
|
||||
if ( ! f?$conns ) return;
|
||||
if ( ! f?$info ) return;
|
||||
if ( ! f$info?$md5 ) return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( ! c?$http ) next;
|
||||
|
||||
c$http$md5 = f$info$md5;
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
##! Identification of file types in HTTP response bodies with file content sniffing.
|
||||
|
||||
@load base/frameworks/notice
|
||||
@load ./main
|
||||
@load ./utils
|
||||
@load ./file-analysis
|
||||
|
||||
module HTTP;
|
||||
|
||||
export {
|
||||
redef enum Notice::Type += {
|
||||
## Indicates when the file extension doesn't seem to match the file
|
||||
## contents.
|
||||
Incorrect_File_Type,
|
||||
};
|
||||
|
||||
redef record Info += {
|
||||
## Mime type of response body identified by content sniffing.
|
||||
mime_type: string &log &optional;
|
||||
};
|
||||
|
||||
## Mapping between mime type strings (without character set) and
|
||||
## regular expressions for URLs.
|
||||
## The :bro:enum:`HTTP::Incorrect_File_Type` notice is generated if the
|
||||
## pattern doesn't match the mime type that was discovered.
|
||||
const mime_types_extensions: table[string] of pattern = {
|
||||
["application/x-dosexec"] = /\.([eE][xX][eE]|[dD][lL][lL])/,
|
||||
} &redef;
|
||||
|
||||
## A pattern for filtering out :bro:enum:`HTTP::Incorrect_File_Type` urls
|
||||
## that are not noteworthy before a notice is created. Each
|
||||
## pattern added should match the complete URL (the matched URLs include
|
||||
## "http://" at the beginning).
|
||||
const ignored_incorrect_file_type_urls = /^$/ &redef;
|
||||
}
|
||||
|
||||
event file_new(f: fa_file) &priority=5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "HTTP" ) return;
|
||||
if ( ! f?$mime_type ) return;
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( ! c?$http ) next;
|
||||
|
||||
c$http$mime_type = f$mime_type;
|
||||
|
||||
local mime_str: string = c$http$mime_type;
|
||||
|
||||
if ( mime_str !in mime_types_extensions ) next;
|
||||
if ( ! c$http?$uri ) next;
|
||||
if ( mime_types_extensions[mime_str] in c$http$uri ) next;
|
||||
|
||||
local url = build_url_http(c$http);
|
||||
|
||||
if ( url == ignored_incorrect_file_type_urls ) next;
|
||||
|
||||
local message = fmt("%s %s %s", mime_str, c$http$method, url);
|
||||
NOTICE([$note=Incorrect_File_Type,
|
||||
$msg=message,
|
||||
$conn=c]);
|
||||
}
|
||||
}
|
||||
|
||||
event file_over_new_connection(f: fa_file, c: connection) &priority=5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "HTTP" ) return;
|
||||
if ( ! f?$mime_type ) return;
|
||||
if ( ! c?$http ) return;
|
||||
|
||||
# Spread the mime around (e.g. for partial content, file_type event only
|
||||
# happens once for the first connection, but if there's subsequent
|
||||
# connections to transfer the same file, they'll be lacking the mime_type
|
||||
# field if we don't do this).
|
||||
c$http$mime_type = f$mime_type;
|
||||
}
|
||||
|
||||
# Tracks byte-range request / partial content response mime types, indexed
|
||||
# by [connection, uri] pairs. This is needed because a person can pipeline
|
||||
# byte-range requests over multiple connections to the same uri. Without
|
||||
# the tracking, only the first request in the pipeline for each connection
|
||||
# would get a mime_type field assigned to it (by the FileAnalysis policy hooks).
|
||||
global partial_types: table[conn_id, string] of string &read_expire=5mins;
|
||||
|
||||
# Priority 4 so that it runs before the handler that will write to http.log.
|
||||
event http_message_done(c: connection, is_orig: bool, stat: http_message_stat)
|
||||
&priority=4
|
||||
{
|
||||
if ( ! c$http$range_request ) return;
|
||||
if ( ! c$http?$uri ) return;
|
||||
|
||||
if ( c$http?$mime_type )
|
||||
{
|
||||
partial_types[c$id, c$http$uri] = c$http$mime_type;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( [c$id, c$http$uri] in partial_types )
|
||||
c$http$mime_type = partial_types[c$id, c$http$uri];
|
||||
}
|
56
scripts/base/protocols/http/files.bro
Normal file
56
scripts/base/protocols/http/files.bro
Normal file
|
@ -0,0 +1,56 @@
|
|||
@load ./main
|
||||
@load ./entities
|
||||
@load ./utils
|
||||
@load base/utils/conn-ids
|
||||
@load base/frameworks/files
|
||||
|
||||
module HTTP;
|
||||
|
||||
export {
|
||||
## Default file handle provider for HTTP.
|
||||
global get_file_handle: function(c: connection, is_orig: bool): string;
|
||||
|
||||
## Default file describer for HTTP.
|
||||
global describe_file: function(f: fa_file): string;
|
||||
}
|
||||
|
||||
function get_file_handle(c: connection, is_orig: bool): string
|
||||
{
|
||||
if ( ! c?$http )
|
||||
return "";
|
||||
|
||||
if ( c$http$range_request && ! is_orig )
|
||||
{
|
||||
# Any multipart responses from the server are pieces of same file
|
||||
# that correspond to range requests, so don't use mime depth to
|
||||
# identify the file.
|
||||
return cat(Analyzer::ANALYZER_HTTP, is_orig, c$id$orig_h, build_url(c$http));
|
||||
}
|
||||
else
|
||||
{
|
||||
local mime_depth = is_orig ? c$http$orig_mime_depth : c$http$resp_mime_depth;
|
||||
return cat(Analyzer::ANALYZER_HTTP, c$start_time, is_orig,
|
||||
c$http$trans_depth, mime_depth, id_string(c$id));
|
||||
}
|
||||
}
|
||||
|
||||
function describe_file(f: fa_file): string
|
||||
{
|
||||
# This shouldn't be needed, but just in case...
|
||||
if ( f$source != "HTTP" )
|
||||
return "";
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
if ( f$conns[cid]?$http )
|
||||
return build_url_http(f$conns[cid]$http);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Files::register_protocol(Analyzer::ANALYZER_HTTP,
|
||||
[$get_file_handle = HTTP::get_file_handle,
|
||||
$describe = HTTP::describe_file]);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
##! Implements base functionality for HTTP analysis. The logging model is
|
||||
##! to log request/response pairs and all relevant metadata together in
|
||||
##! Implements base functionality for HTTP analysis. The logging model is
|
||||
##! to log request/response pairs and all relevant metadata together in
|
||||
##! a single record.
|
||||
|
||||
@load base/utils/numbers
|
||||
|
@ -15,10 +15,10 @@ export {
|
|||
## Placeholder.
|
||||
EMPTY
|
||||
};
|
||||
|
||||
|
||||
## This setting changes if passwords used in Basic-Auth are captured or not.
|
||||
const default_capture_password = F &redef;
|
||||
|
||||
|
||||
type Info: record {
|
||||
## Timestamp for when the request happened.
|
||||
ts: time &log;
|
||||
|
@ -26,7 +26,7 @@ export {
|
|||
uid: string &log;
|
||||
## The connection's 4-tuple of endpoint addresses/ports.
|
||||
id: conn_id &log;
|
||||
## Represents the pipelined depth into the connection of this
|
||||
## Represents the pipelined depth into the connection of this
|
||||
## request/response transaction.
|
||||
trans_depth: count &log;
|
||||
## Verb used in the HTTP request (GET, POST, HEAD, etc.).
|
||||
|
@ -60,24 +60,24 @@ export {
|
|||
## A set of indicators of various attributes discovered and
|
||||
## related to a particular request/response pair.
|
||||
tags: set[Tags] &log;
|
||||
|
||||
|
||||
## Username if basic-auth is performed for the request.
|
||||
username: string &log &optional;
|
||||
## Password if basic-auth is performed for the request.
|
||||
password: string &log &optional;
|
||||
|
||||
|
||||
## Determines if the password will be captured for this request.
|
||||
capture_password: bool &default=default_capture_password;
|
||||
|
||||
|
||||
## All of the headers that may indicate if the request was proxied.
|
||||
proxied: set[string] &log &optional;
|
||||
|
||||
## Indicates if this request can assume 206 partial content in
|
||||
## response.
|
||||
range_request: bool &default=F;
|
||||
range_request: bool &default=F;
|
||||
};
|
||||
|
||||
## Structure to maintain state for an HTTP connection with multiple
|
||||
|
||||
## Structure to maintain state for an HTTP connection with multiple
|
||||
## requests and responses.
|
||||
type State: record {
|
||||
## Pending requests.
|
||||
|
@ -87,7 +87,7 @@ export {
|
|||
## Current response in the pending queue.
|
||||
current_response: count &default=0;
|
||||
};
|
||||
|
||||
|
||||
## A list of HTTP headers typically used to indicate proxied requests.
|
||||
const proxy_headers: set[string] = {
|
||||
"FORWARDED",
|
||||
|
@ -100,8 +100,8 @@ export {
|
|||
} &redef;
|
||||
|
||||
## A list of HTTP methods. Other methods will generate a weird. Note
|
||||
## that the HTTP analyzer will only accept methods consisting solely
|
||||
## of letters ``[A-Za-z]``.
|
||||
## that the HTTP analyzer will only accept methods consisting solely
|
||||
## of letters ``[A-Za-z]``.
|
||||
const http_methods: set[string] = {
|
||||
"GET", "POST", "HEAD", "OPTIONS",
|
||||
"PUT", "DELETE", "TRACE", "CONNECT",
|
||||
|
@ -111,8 +111,8 @@ export {
|
|||
"POLL", "REPORT", "SUBSCRIBE", "BMOVE",
|
||||
"SEARCH"
|
||||
} &redef;
|
||||
|
||||
## Event that can be handled to access the HTTP record as it is sent on
|
||||
|
||||
## Event that can be handled to access the HTTP record as it is sent on
|
||||
## to the logging framework.
|
||||
global log_http: event(rec: Info);
|
||||
}
|
||||
|
@ -147,12 +147,12 @@ function new_http_session(c: connection): Info
|
|||
tmp$ts=network_time();
|
||||
tmp$uid=c$uid;
|
||||
tmp$id=c$id;
|
||||
# $current_request is set prior to the Info record creation so we
|
||||
# $current_request is set prior to the Info record creation so we
|
||||
# can use the value directly here.
|
||||
tmp$trans_depth = c$http_state$current_request;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
function set_state(c: connection, request: bool, is_orig: bool)
|
||||
{
|
||||
if ( ! c?$http_state )
|
||||
|
@ -160,19 +160,19 @@ function set_state(c: connection, request: bool, is_orig: bool)
|
|||
local s: State;
|
||||
c$http_state = s;
|
||||
}
|
||||
|
||||
|
||||
# These deal with new requests and responses.
|
||||
if ( request || c$http_state$current_request !in c$http_state$pending )
|
||||
c$http_state$pending[c$http_state$current_request] = new_http_session(c);
|
||||
if ( ! is_orig && c$http_state$current_response !in c$http_state$pending )
|
||||
c$http_state$pending[c$http_state$current_response] = new_http_session(c);
|
||||
|
||||
|
||||
if ( is_orig )
|
||||
c$http = c$http_state$pending[c$http_state$current_request];
|
||||
else
|
||||
c$http = c$http_state$pending[c$http_state$current_response];
|
||||
}
|
||||
|
||||
|
||||
event http_request(c: connection, method: string, original_URI: string,
|
||||
unescaped_URI: string, version: string) &priority=5
|
||||
{
|
||||
|
@ -181,17 +181,17 @@ event http_request(c: connection, method: string, original_URI: string,
|
|||
local s: State;
|
||||
c$http_state = s;
|
||||
}
|
||||
|
||||
|
||||
++c$http_state$current_request;
|
||||
set_state(c, T, T);
|
||||
|
||||
|
||||
c$http$method = method;
|
||||
c$http$uri = unescaped_URI;
|
||||
|
||||
if ( method !in http_methods )
|
||||
event conn_weird("unknown_HTTP_method", c, method);
|
||||
}
|
||||
|
||||
|
||||
event http_reply(c: connection, version: string, code: count, reason: string) &priority=5
|
||||
{
|
||||
if ( ! c?$http_state )
|
||||
|
@ -199,7 +199,7 @@ event http_reply(c: connection, version: string, code: count, reason: string) &p
|
|||
local s: State;
|
||||
c$http_state = s;
|
||||
}
|
||||
|
||||
|
||||
# If the last response was an informational 1xx, we're still expecting
|
||||
# the real response to the request, so don't create a new Info record yet.
|
||||
if ( c$http_state$current_response !in c$http_state$pending ||
|
||||
|
@ -207,7 +207,7 @@ event http_reply(c: connection, version: string, code: count, reason: string) &p
|
|||
! code_in_range(c$http_state$pending[c$http_state$current_response]$status_code, 100, 199)) )
|
||||
++c$http_state$current_response;
|
||||
set_state(c, F, F);
|
||||
|
||||
|
||||
c$http$status_code = code;
|
||||
c$http$status_msg = reason;
|
||||
if ( code_in_range(code, 100, 199) )
|
||||
|
@ -216,33 +216,33 @@ event http_reply(c: connection, version: string, code: count, reason: string) &p
|
|||
c$http$info_msg = reason;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=5
|
||||
{
|
||||
set_state(c, F, is_orig);
|
||||
|
||||
|
||||
if ( is_orig ) # client headers
|
||||
{
|
||||
if ( name == "REFERER" )
|
||||
c$http$referrer = value;
|
||||
|
||||
|
||||
else if ( name == "HOST" )
|
||||
# The split is done to remove the occasional port value that shows up here.
|
||||
c$http$host = split1(value, /:/)[1];
|
||||
|
||||
else if ( name == "RANGE" )
|
||||
c$http$range_request = T;
|
||||
|
||||
|
||||
else if ( name == "USER-AGENT" )
|
||||
c$http$user_agent = value;
|
||||
|
||||
|
||||
else if ( name in proxy_headers )
|
||||
{
|
||||
if ( ! c$http?$proxied )
|
||||
c$http$proxied = set();
|
||||
add c$http$proxied[fmt("%s -> %s", name, value)];
|
||||
}
|
||||
|
||||
|
||||
else if ( name == "AUTHORIZATION" )
|
||||
{
|
||||
if ( /^[bB][aA][sS][iI][cC] / in value )
|
||||
|
@ -264,25 +264,19 @@ event http_header(c: connection, is_orig: bool, name: string, value: string) &pr
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
else # server headers
|
||||
{
|
||||
if ( name == "CONTENT-DISPOSITION" &&
|
||||
/[fF][iI][lL][eE][nN][aA][mM][eE]/ in value )
|
||||
c$http$filename = extract_filename_from_content_disposition(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
event http_message_done(c: connection, is_orig: bool, stat: http_message_stat) &priority = 5
|
||||
{
|
||||
set_state(c, F, is_orig);
|
||||
|
||||
|
||||
if ( is_orig )
|
||||
c$http$request_body_len = stat$body_length;
|
||||
else
|
||||
c$http$response_body_len = stat$body_length;
|
||||
}
|
||||
|
||||
|
||||
event http_message_done(c: connection, is_orig: bool, stat: http_message_stat) &priority = -5
|
||||
{
|
||||
# The reply body is done so we're ready to log.
|
||||
|
@ -311,4 +305,4 @@ event connection_state_remove(c: connection) &priority=-5
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -32,6 +32,9 @@ export {
|
|||
##
|
||||
## Returns: A URL prefixed with "http://".
|
||||
global build_url_http: function(rec: Info): string;
|
||||
|
||||
## Create an extremely shortened representation of a log line.
|
||||
global describe: function(rec: Info): string;
|
||||
}
|
||||
|
||||
|
||||
|
@ -62,3 +65,8 @@ function build_url_http(rec: Info): string
|
|||
{
|
||||
return fmt("http://%s", build_url(rec));
|
||||
}
|
||||
|
||||
function describe(rec: Info): string
|
||||
{
|
||||
return build_url_http(rec);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@load ./main
|
||||
@load ./dcc-send
|
||||
@load ./file-analysis
|
||||
@load ./files
|
||||
|
||||
@load-sigs ./dpd.sig
|
|
@ -2,7 +2,7 @@
|
|||
##!
|
||||
##! There is a major problem with this script in the cluster context because
|
||||
##! we might see A send B a message that a DCC connection is to be expected,
|
||||
##! but that connection will actually be between B and C which could be
|
||||
##! but that connection will actually be between B and C which could be
|
||||
##! analyzed on a different worker.
|
||||
##!
|
||||
|
||||
|
@ -15,12 +15,6 @@
|
|||
module IRC;
|
||||
|
||||
export {
|
||||
## Pattern of file mime types to extract from IRC DCC file transfers.
|
||||
const extract_file_types = /NO_DEFAULT/ &redef;
|
||||
|
||||
## On-disk prefix for files to be extracted from IRC DCC file transfers.
|
||||
const extraction_prefix = "irc-dcc-item" &redef;
|
||||
|
||||
redef record Info += {
|
||||
## DCC filename requested.
|
||||
dcc_file_name: string &log &optional;
|
||||
|
@ -28,101 +22,10 @@ export {
|
|||
dcc_file_size: count &log &optional;
|
||||
## Sniffed mime type of the file.
|
||||
dcc_mime_type: string &log &optional;
|
||||
|
||||
## The file handle for the file to be extracted
|
||||
extraction_file: string &log &optional;
|
||||
|
||||
## A boolean to indicate if the current file transfer should be extracted.
|
||||
extract_file: bool &default=F;
|
||||
};
|
||||
}
|
||||
|
||||
global dcc_expected_transfers: table[addr, port] of Info &read_expire=5mins;
|
||||
|
||||
function set_dcc_mime(f: fa_file)
|
||||
{
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( [cid$resp_h, cid$resp_p] !in dcc_expected_transfers ) next;
|
||||
|
||||
local s = dcc_expected_transfers[cid$resp_h, cid$resp_p];
|
||||
|
||||
s$dcc_mime_type = f$mime_type;
|
||||
}
|
||||
}
|
||||
|
||||
function set_dcc_extraction_file(f: fa_file, filename: string)
|
||||
{
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( [cid$resp_h, cid$resp_p] !in dcc_expected_transfers ) next;
|
||||
|
||||
local s = dcc_expected_transfers[cid$resp_h, cid$resp_p];
|
||||
|
||||
s$extraction_file = filename;
|
||||
}
|
||||
}
|
||||
|
||||
function get_extraction_name(f: fa_file): string
|
||||
{
|
||||
local r = fmt("%s-%s.dat", extraction_prefix, f$id);
|
||||
return r;
|
||||
}
|
||||
|
||||
# this handler sets the IRC::Info mime type
|
||||
event file_new(f: fa_file) &priority=5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "IRC_DATA" ) return;
|
||||
if ( ! f?$mime_type ) return;
|
||||
|
||||
set_dcc_mime(f);
|
||||
}
|
||||
|
||||
# this handler check if file extraction is desired
|
||||
event file_new(f: fa_file) &priority=5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "IRC_DATA" ) return;
|
||||
|
||||
local fname: string;
|
||||
|
||||
if ( f?$mime_type && extract_file_types in f$mime_type )
|
||||
{
|
||||
fname = get_extraction_name(f);
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_EXTRACT,
|
||||
$extract_filename=fname]);
|
||||
set_dcc_extraction_file(f, fname);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( [cid$resp_h, cid$resp_p] !in dcc_expected_transfers ) next;
|
||||
|
||||
local s = dcc_expected_transfers[cid$resp_h, cid$resp_p];
|
||||
|
||||
if ( ! s$extract_file ) next;
|
||||
|
||||
fname = get_extraction_name(f);
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_EXTRACT,
|
||||
$extract_filename=fname]);
|
||||
s$extraction_file = fname;
|
||||
return;
|
||||
}
|
||||
}
|
||||
global dcc_expected_transfers: table[addr, port] of Info &synchronized &read_expire=5mins;
|
||||
|
||||
function log_dcc(f: fa_file)
|
||||
{
|
||||
|
@ -141,24 +44,21 @@ function log_dcc(f: fa_file)
|
|||
Log::write(IRC::LOG, irc);
|
||||
irc$command = tmp;
|
||||
|
||||
# Delete these values in case another DCC transfer
|
||||
# Delete these values in case another DCC transfer
|
||||
# happens during the IRC session.
|
||||
delete irc$extract_file;
|
||||
delete irc$extraction_file;
|
||||
delete irc$dcc_file_name;
|
||||
delete irc$dcc_file_size;
|
||||
delete irc$dcc_mime_type;
|
||||
|
||||
delete dcc_expected_transfers[cid$resp_h, cid$resp_p];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event file_new(f: fa_file) &priority=-5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "IRC_DATA" ) return;
|
||||
|
||||
log_dcc(f);
|
||||
if ( f$source == "IRC_DATA" )
|
||||
log_dcc(f);
|
||||
}
|
||||
|
||||
event irc_dcc_message(c: connection, is_orig: bool,
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
@load ./dcc-send.bro
|
||||
@load base/utils/conn-ids
|
||||
@load base/frameworks/file-analysis/main
|
||||
|
||||
module IRC;
|
||||
|
||||
export {
|
||||
## Default file handle provider for IRC.
|
||||
global get_file_handle: function(c: connection, is_orig: bool): string;
|
||||
}
|
||||
|
||||
function get_file_handle(c: connection, is_orig: bool): string
|
||||
{
|
||||
if ( is_orig ) return "";
|
||||
return cat(Analyzer::ANALYZER_IRC_DATA, " ", c$start_time, " ", id_string(c$id));
|
||||
}
|
||||
|
||||
module GLOBAL;
|
||||
|
||||
event get_file_handle(tag: Analyzer::Tag, c: connection, is_orig: bool)
|
||||
&priority=5
|
||||
{
|
||||
if ( tag != Analyzer::ANALYZER_IRC_DATA ) return;
|
||||
set_file_handle(IRC::get_file_handle(c, is_orig));
|
||||
}
|
39
scripts/base/protocols/irc/files.bro
Normal file
39
scripts/base/protocols/irc/files.bro
Normal file
|
@ -0,0 +1,39 @@
|
|||
@load ./dcc-send
|
||||
@load base/utils/conn-ids
|
||||
@load base/frameworks/files
|
||||
|
||||
module IRC;
|
||||
|
||||
export {
|
||||
redef record Info += {
|
||||
## File unique ID.
|
||||
fuid: string &log &optional;
|
||||
};
|
||||
|
||||
## Default file handle provider for IRC.
|
||||
global get_file_handle: function(c: connection, is_orig: bool): string;
|
||||
}
|
||||
|
||||
function get_file_handle(c: connection, is_orig: bool): string
|
||||
{
|
||||
return cat(Analyzer::ANALYZER_IRC_DATA, c$start_time, c$id, is_orig);
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Files::register_protocol(Analyzer::ANALYZER_IRC_DATA,
|
||||
[$get_file_handle = IRC::get_file_handle]);
|
||||
}
|
||||
|
||||
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( [c$id$resp_h, c$id$resp_p] !in dcc_expected_transfers )
|
||||
return;
|
||||
|
||||
local irc = dcc_expected_transfers[c$id$resp_h, c$id$resp_p];
|
||||
irc$fuid = f$id;
|
||||
if ( irc?$dcc_file_name )
|
||||
f$info$filename = irc$dcc_file_name;
|
||||
if ( f?$mime_type )
|
||||
irc$dcc_mime_type = f$mime_type;
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
@load ./main
|
||||
@load ./entities
|
||||
@load ./entities-excerpt
|
||||
@load ./file-analysis
|
||||
@load ./files
|
||||
|
||||
@load-sigs ./dpd.sig
|
|
@ -1,37 +0,0 @@
|
|||
##! This script is for optionally adding a body excerpt to the SMTP
|
||||
##! entities log.
|
||||
|
||||
@load ./entities
|
||||
|
||||
module SMTP;
|
||||
|
||||
export {
|
||||
redef record SMTP::EntityInfo += {
|
||||
## The entity body excerpt.
|
||||
excerpt: string &log &default="";
|
||||
};
|
||||
|
||||
## This is the default value for how much of the entity body should be
|
||||
## included for all MIME entities. The lesser of this value and
|
||||
## :bro:see:`default_file_bof_buffer_size` will be used.
|
||||
const default_entity_excerpt_len = 0 &redef;
|
||||
}
|
||||
|
||||
event file_new(f: fa_file) &priority=5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "SMTP" ) return;
|
||||
if ( ! f?$bof_buffer ) return;
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( ! c?$smtp ) next;
|
||||
|
||||
if ( default_entity_excerpt_len > 0 )
|
||||
c$smtp$current_entity$excerpt =
|
||||
f$bof_buffer[0:default_entity_excerpt_len];
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
##! Analysis and logging for MIME entities found in SMTP sessions.
|
||||
|
||||
@load base/frameworks/files
|
||||
@load base/utils/strings
|
||||
@load base/utils/files
|
||||
@load ./main
|
||||
|
@ -7,217 +8,55 @@
|
|||
module SMTP;
|
||||
|
||||
export {
|
||||
redef enum Log::ID += { ENTITIES_LOG };
|
||||
|
||||
type EntityInfo: record {
|
||||
## This is the timestamp of when the MIME content transfer began.
|
||||
ts: time &log;
|
||||
uid: string &log;
|
||||
id: conn_id &log;
|
||||
## A count to represent the depth of this message transaction in a
|
||||
## single connection where multiple messages were transferred.
|
||||
trans_depth: count &log;
|
||||
## The filename seen in the Content-Disposition header.
|
||||
filename: string &log &optional;
|
||||
## Track how many bytes of the MIME encoded file have been seen.
|
||||
content_len: count &log &default=0;
|
||||
## The mime type of the entity discovered through magic bytes identification.
|
||||
mime_type: string &log &optional;
|
||||
|
||||
## The calculated MD5 sum for the MIME entity.
|
||||
md5: string &log &optional;
|
||||
## Optionally calculate the file's MD5 sum. Must be set prior to the
|
||||
## first data chunk being see in an event.
|
||||
calc_md5: bool &default=F;
|
||||
|
||||
## Optionally write the file to disk. Must be set prior to first
|
||||
## data chunk being seen in an event.
|
||||
extract_file: bool &default=F;
|
||||
## Store the file handle here for the file currently being extracted.
|
||||
extraction_file: string &log &optional;
|
||||
type Entity: record {
|
||||
## Filename for the entity if discovered from a header.
|
||||
filename: string &optional;
|
||||
};
|
||||
|
||||
redef record Info += {
|
||||
## The in-progress entity information.
|
||||
current_entity: EntityInfo &optional;
|
||||
## The current entity being seen.
|
||||
entity: Entity &optional;
|
||||
};
|
||||
|
||||
redef record State += {
|
||||
## Track the number of MIME encoded files transferred during a session.
|
||||
mime_level: count &default=0;
|
||||
## Track the number of MIME encoded files transferred
|
||||
## during a session.
|
||||
mime_depth: count &default=0;
|
||||
};
|
||||
|
||||
## Generate MD5 sums for these filetypes.
|
||||
const generate_md5 = /application\/x-dosexec/ # Windows and DOS executables
|
||||
| /application\/x-executable/ # *NIX executable binary
|
||||
&redef;
|
||||
|
||||
## Pattern of file mime types to extract from MIME bodies.
|
||||
const extract_file_types = /NO_DEFAULT/ &redef;
|
||||
|
||||
## The on-disk prefix for files to be extracted from MIME entity bodies.
|
||||
const extraction_prefix = "smtp-entity" &redef;
|
||||
|
||||
## If set, never generate MD5s. This is mainly for testing purposes to create
|
||||
## reproducable output in the case that the decision whether to create
|
||||
## checksums depends on environment specifics.
|
||||
const never_calc_md5 = F &redef;
|
||||
|
||||
global log_mime: event(rec: EntityInfo);
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(SMTP::ENTITIES_LOG, [$columns=EntityInfo, $ev=log_mime]);
|
||||
}
|
||||
|
||||
function set_session(c: connection, new_entity: bool)
|
||||
{
|
||||
if ( ! c$smtp?$current_entity || new_entity )
|
||||
{
|
||||
local info: EntityInfo;
|
||||
info$ts=network_time();
|
||||
info$uid=c$uid;
|
||||
info$id=c$id;
|
||||
info$trans_depth=c$smtp$trans_depth;
|
||||
|
||||
c$smtp$current_entity = info;
|
||||
++c$smtp_state$mime_level;
|
||||
}
|
||||
}
|
||||
|
||||
function get_extraction_name(f: fa_file): string
|
||||
{
|
||||
local r = fmt("%s-%s.dat", extraction_prefix, f$id);
|
||||
return r;
|
||||
}
|
||||
|
||||
event mime_begin_entity(c: connection) &priority=10
|
||||
{
|
||||
if ( ! c?$smtp ) return;
|
||||
|
||||
set_session(c, T);
|
||||
c$smtp$entity = Entity();
|
||||
++c$smtp_state$mime_depth;
|
||||
}
|
||||
|
||||
event file_new(f: fa_file) &priority=5
|
||||
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "SMTP" ) return;
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
local fname: string;
|
||||
local extracting: bool = F;
|
||||
|
||||
for ( cid in f$conns )
|
||||
if ( f$source == "SMTP" && c?$smtp )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( ! c?$smtp ) next;
|
||||
if ( ! c$smtp?$current_entity ) next;
|
||||
|
||||
if ( c$smtp$current_entity$extract_file )
|
||||
{
|
||||
if ( ! extracting )
|
||||
{
|
||||
fname = get_extraction_name(f);
|
||||
FileAnalysis::add_analyzer(f,
|
||||
[$tag=FileAnalysis::ANALYZER_EXTRACT,
|
||||
$extract_filename=fname]);
|
||||
extracting = T;
|
||||
}
|
||||
|
||||
c$smtp$current_entity$extraction_file = fname;
|
||||
}
|
||||
|
||||
if ( c$smtp$current_entity$calc_md5 )
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_MD5]);
|
||||
if ( c$smtp?$entity && c$smtp$entity?$filename )
|
||||
f$info$filename = c$smtp$entity$filename;
|
||||
f$info$depth = c$smtp_state$mime_depth;
|
||||
}
|
||||
}
|
||||
|
||||
function check_extract_by_type(f: fa_file)
|
||||
event mime_one_header(c: connection, h: mime_header_rec) &priority=5
|
||||
{
|
||||
if ( extract_file_types !in f$mime_type ) return;
|
||||
|
||||
if ( f?$info && FileAnalysis::ANALYZER_EXTRACT in f$info$analyzers )
|
||||
if ( ! c?$smtp )
|
||||
return;
|
||||
|
||||
local fname: string = get_extraction_name(f);
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_EXTRACT,
|
||||
$extract_filename=fname]);
|
||||
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
if ( ! c?$smtp ) next;
|
||||
c$smtp$current_entity$extraction_file = fname;
|
||||
}
|
||||
}
|
||||
|
||||
function check_md5_by_type(f: fa_file)
|
||||
{
|
||||
if ( never_calc_md5 ) return;
|
||||
if ( generate_md5 !in f$mime_type ) return;
|
||||
|
||||
FileAnalysis::add_analyzer(f, [$tag=FileAnalysis::ANALYZER_MD5]);
|
||||
}
|
||||
|
||||
event file_new(f: fa_file) &priority=5
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "SMTP" ) return;
|
||||
if ( ! f?$mime_type ) return;
|
||||
|
||||
if ( f?$conns )
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( ! c?$smtp ) next;
|
||||
if ( ! c$smtp?$current_entity ) next;
|
||||
|
||||
c$smtp$current_entity$mime_type = f$mime_type;
|
||||
}
|
||||
|
||||
check_extract_by_type(f);
|
||||
check_md5_by_type(f);
|
||||
}
|
||||
|
||||
event file_state_remove(f: fa_file) &priority=4
|
||||
{
|
||||
if ( ! f?$source ) return;
|
||||
if ( f$source != "SMTP" ) return;
|
||||
if ( ! f?$conns ) return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c: connection = f$conns[cid];
|
||||
|
||||
if ( ! c?$smtp ) next;
|
||||
if ( ! c$smtp?$current_entity ) next;
|
||||
# Only log if there was some content.
|
||||
if ( f$seen_bytes == 0 ) next;
|
||||
|
||||
if ( f?$info && f$info?$md5 )
|
||||
c$smtp$current_entity$md5 = f$info$md5;
|
||||
|
||||
c$smtp$current_entity$content_len = f$seen_bytes;
|
||||
Log::write(SMTP::ENTITIES_LOG, c$smtp$current_entity);
|
||||
delete c$smtp$current_entity;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event mime_one_header(c: connection, h: mime_header_rec)
|
||||
{
|
||||
if ( ! c?$smtp ) return;
|
||||
|
||||
if ( h$name == "CONTENT-DISPOSITION" &&
|
||||
/[fF][iI][lL][eE][nN][aA][mM][eE]/ in h$value )
|
||||
c$smtp$current_entity$filename = extract_filename_from_content_disposition(h$value);
|
||||
c$smtp$entity$filename = extract_filename_from_content_disposition(h$value);
|
||||
|
||||
if ( h$name == "CONTENT-TYPE" &&
|
||||
/[nN][aA][mM][eE][:blank:]*=/ in h$value )
|
||||
c$smtp$current_entity$filename = extract_filename_from_content_disposition(h$value);
|
||||
c$smtp$entity$filename = extract_filename_from_content_disposition(h$value);
|
||||
}
|
||||
|
||||
event mime_end_entity(c: connection) &priority=5
|
||||
{
|
||||
if ( c?$smtp && c$smtp?$entity )
|
||||
delete c$smtp$entity;
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
@load ./main
|
||||
@load ./entities
|
||||
@load base/utils/conn-ids
|
||||
@load base/frameworks/file-analysis/main
|
||||
|
||||
module SMTP;
|
||||
|
||||
export {
|
||||
## Default file handle provider for SMTP.
|
||||
global get_file_handle: function(c: connection, is_orig: bool): string;
|
||||
}
|
||||
|
||||
function get_file_handle(c: connection, is_orig: bool): string
|
||||
{
|
||||
if ( ! c?$smtp ) return "";
|
||||
return cat(Analyzer::ANALYZER_SMTP, " ", c$start_time, " ", c$smtp$trans_depth, " ",
|
||||
c$smtp_state$mime_level);
|
||||
}
|
||||
|
||||
module GLOBAL;
|
||||
|
||||
event get_file_handle(tag: Analyzer::Tag, c: connection, is_orig: bool)
|
||||
&priority=5
|
||||
{
|
||||
if ( tag != Analyzer::ANALYZER_SMTP ) return;
|
||||
set_file_handle(SMTP::get_file_handle(c, is_orig));
|
||||
}
|
53
scripts/base/protocols/smtp/files.bro
Normal file
53
scripts/base/protocols/smtp/files.bro
Normal file
|
@ -0,0 +1,53 @@
|
|||
@load ./main
|
||||
@load ./entities
|
||||
@load base/utils/conn-ids
|
||||
@load base/frameworks/files
|
||||
|
||||
module SMTP;
|
||||
|
||||
export {
|
||||
redef record Info += {
|
||||
## An ordered vector of file unique IDs seen attached to
|
||||
## the message.
|
||||
fuids: vector of string &log &default=string_vec();
|
||||
};
|
||||
|
||||
## Default file handle provider for SMTP.
|
||||
global get_file_handle: function(c: connection, is_orig: bool): string;
|
||||
|
||||
## Default file describer for SMTP.
|
||||
global describe_file: function(f: fa_file): string;
|
||||
}
|
||||
|
||||
function get_file_handle(c: connection, is_orig: bool): string
|
||||
{
|
||||
return cat(Analyzer::ANALYZER_SMTP, c$start_time, c$smtp$trans_depth,
|
||||
c$smtp_state$mime_depth);
|
||||
}
|
||||
|
||||
function describe_file(f: fa_file): string
|
||||
{
|
||||
# This shouldn't be needed, but just in case...
|
||||
if ( f$source != "SMTP" )
|
||||
return "";
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local c = f$conns[cid];
|
||||
return SMTP::describe(c$smtp);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Files::register_protocol(Analyzer::ANALYZER_SMTP,
|
||||
[$get_file_handle = SMTP::get_file_handle,
|
||||
$describe = SMTP::describe_file]);
|
||||
}
|
||||
|
||||
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( c?$smtp )
|
||||
c$smtp$fuids[|c$smtp$fuids|] = f$id;
|
||||
}
|
|
@ -72,7 +72,10 @@ export {
|
|||
## ALL_HOSTS - always capture the entire path.
|
||||
## NO_HOSTS - never capture the path.
|
||||
const mail_path_capture = ALL_HOSTS &redef;
|
||||
|
||||
|
||||
## Create an extremely shortened representation of a log line.
|
||||
global describe: function(rec: Info): string;
|
||||
|
||||
global log_smtp: event(rec: Info);
|
||||
}
|
||||
|
||||
|
@ -271,3 +274,29 @@ event connection_state_remove(c: connection) &priority=-5
|
|||
if ( c?$smtp )
|
||||
smtp_message(c);
|
||||
}
|
||||
|
||||
function describe(rec: Info): string
|
||||
{
|
||||
if ( rec?$mailfrom && rec?$rcptto )
|
||||
{
|
||||
local one_to = "";
|
||||
for ( to in rec$rcptto )
|
||||
{
|
||||
one_to = to;
|
||||
break;
|
||||
}
|
||||
local abbrev_subject = "";
|
||||
if ( rec?$subject )
|
||||
{
|
||||
if ( |rec$subject| > 20 )
|
||||
{
|
||||
abbrev_subject = rec$subject[0:20] + "...";
|
||||
}
|
||||
}
|
||||
|
||||
return fmt("%s -> %s%s%s", rec$mailfrom, one_to,
|
||||
(|rec$rcptto|>1 ? fmt(" (plus %d others)", |rec$rcptto|-1) : ""),
|
||||
(abbrev_subject != "" ? fmt(": %s", abbrev_subject) : ""));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue