mirror of
https://github.com/zeek/zeek.git
synced 2025-10-06 16:48:19 +00:00
More script updates.
This commit is contained in:
parent
0a151882ae
commit
adec99751d
10 changed files with 472 additions and 405 deletions
|
@ -1,4 +1,5 @@
|
|||
##! This script enables logging of packet segment data. The amount of
|
||||
##! This script enables logging of packet segment data when a protocol
|
||||
##! parsing violation is encountered. The amount of
|
||||
##! data from the packet logged is set by the packet_segment_size variable.
|
||||
##! A caveat to logging packet data is that in some cases, the packet may
|
||||
##! not be the packet that actually caused the protocol violation. For this
|
||||
|
|
|
@ -165,7 +165,10 @@ function ftp_message(s: State)
|
|||
|
||||
s$ts=s$cmdarg$ts;
|
||||
s$command=s$cmdarg$cmd;
|
||||
s$arg=arg;
|
||||
if ( arg == "" )
|
||||
delete s$arg;
|
||||
else
|
||||
s$arg=arg;
|
||||
|
||||
# TODO: does the framework do this atomicly or do I need the copy?
|
||||
Log::write(FTP, copy(s));
|
||||
|
@ -174,12 +177,13 @@ function ftp_message(s: State)
|
|||
# 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.
|
||||
# TODO: change these to blank or remove the field when moving to the new
|
||||
# logging framework
|
||||
s$mime_type = "";
|
||||
s$mime_desc = "";
|
||||
s$file_size = 0;
|
||||
s$tags = set();
|
||||
delete s$mime_type;
|
||||
delete s$mime_desc;
|
||||
delete s$file_size;
|
||||
# Tags are cleared everytime too. Maybe that's not a good idea?
|
||||
# TODO: deleting sets with a &default seems to be broken.
|
||||
#delete s$tags;
|
||||
s$tags=set();
|
||||
}
|
||||
|
||||
event ftp_request(c: connection, command: string, arg: string) &priority=5
|
||||
|
@ -333,7 +337,6 @@ event file_transferred(c: connection, prefix: string, descr: string,
|
|||
mime_type: string) &priority=5
|
||||
{
|
||||
local id = c$id;
|
||||
print descr;
|
||||
if ( [id$resp_h, id$resp_p] in ftp_data_expected )
|
||||
{
|
||||
local expected = ftp_data_expected[id$resp_h, id$resp_p];
|
||||
|
|
|
@ -15,7 +15,7 @@ export {
|
|||
redef record State += {
|
||||
extracted_filename: string &log &optional;
|
||||
|
||||
extract_file: bool &default=T;
|
||||
extract_file: bool &default=F;
|
||||
};
|
||||
|
||||
redef enum Tags += { EXTRACTED_FILE };
|
||||
|
@ -59,3 +59,9 @@ event file_transferred(c: connection, prefix: string, descr: string,
|
|||
set_contents_file(id, CONTENTS_ORIG, fh);
|
||||
}
|
||||
}
|
||||
|
||||
event log_ftp(rec: State) &priority=-10
|
||||
{
|
||||
delete rec$extracted_filename;
|
||||
delete rec$extract_file;
|
||||
}
|
|
@ -98,34 +98,15 @@ function set_state(c: connection, request: bool, initial: bool)
|
|||
# TODO: need some FIFO operations on vectors and/or sets.
|
||||
c$http_pending[|c$http_pending|+1] = new_http_session(c);
|
||||
|
||||
if ( request )
|
||||
{
|
||||
# Save the existing c$http back to the correct place in http_pending.
|
||||
# TODO: understand why this isn't just updated correctly since it's
|
||||
# all pointers internally.
|
||||
if ( ! initial )
|
||||
c$http_pending[|c$http_pending|] = c$http;
|
||||
c$http = c$http_pending[|c$http_pending|];
|
||||
}
|
||||
if ( c$http_current_response in c$http_pending )
|
||||
c$http = c$http_pending[c$http_current_response];
|
||||
else
|
||||
{
|
||||
if ( ! initial )
|
||||
c$http_pending[c$http_current_response] = c$http;
|
||||
if ( c$http_current_response in c$http_pending )
|
||||
{
|
||||
c$http = c$http_pending[c$http_current_response];
|
||||
}
|
||||
else
|
||||
c$http = c$http_pending[|c$http_pending|];
|
||||
}
|
||||
|
||||
#print c$http_pending;
|
||||
c$http = c$http_pending[|c$http_pending|];
|
||||
}
|
||||
|
||||
event http_request(c: connection, method: string, original_URI: string,
|
||||
unescaped_URI: string, version: string) &priority=5
|
||||
{
|
||||
#print "http_request";
|
||||
set_state(c, T, T);
|
||||
|
||||
c$http$method = method;
|
||||
|
@ -134,7 +115,6 @@ event http_request(c: connection, method: string, original_URI: string,
|
|||
|
||||
event http_reply(c: connection, version: string, code: count, reason: string) &priority=5
|
||||
{
|
||||
#print "http reply";
|
||||
++c$http_current_response;
|
||||
set_state(c, F, T);
|
||||
|
||||
|
@ -144,7 +124,6 @@ event http_reply(c: connection, version: string, code: count, reason: string) &p
|
|||
|
||||
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=5
|
||||
{
|
||||
#print "http_header";
|
||||
set_state(c, is_orig, F);
|
||||
|
||||
if ( is_orig ) # client headers
|
||||
|
@ -167,7 +146,7 @@ event http_header(c: connection, is_orig: bool, name: string, value: string) &pr
|
|||
else # server headers
|
||||
{
|
||||
if ( name == "CONTENT-LENGTH" )
|
||||
c$http$response_content_length = to_count(value);
|
||||
c$http$response_content_length = to_count(strip(value));
|
||||
}
|
||||
|
||||
#if ( is_orig )
|
||||
|
@ -184,7 +163,6 @@ event http_header(c: connection, is_orig: bool, name: string, value: string) &pr
|
|||
|
||||
event http_message_done(c: connection, is_orig: bool, stat: http_message_stat) &priority=-5
|
||||
{
|
||||
#print "message done";
|
||||
set_state(c, is_orig, F);
|
||||
|
||||
if ( is_orig )
|
||||
|
@ -196,7 +174,6 @@ event http_message_done(c: connection, is_orig: bool, stat: http_message_stat) &
|
|||
{
|
||||
if ( c$http$log_point == AFTER_REPLY )
|
||||
{
|
||||
#print "logging";
|
||||
Log::write(HTTP, c$http);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,7 +114,6 @@ redef new_notice_tag = function(): string
|
|||
event bro_init()
|
||||
{
|
||||
Log::create_stream(NOTICE_LOG, [$columns=Info, $ev=log_notice]);
|
||||
Log::add_default_filter(NOTICE_LOG);
|
||||
}
|
||||
|
||||
function add_notice_tag(c: connection): string
|
||||
|
|
|
@ -17,9 +17,9 @@ event log_smtp(rec: Info)
|
|||
# This falls apart a bit in the cases where a webmail client includes the
|
||||
# IP address of the client in a header. This will be compensated for
|
||||
# later with more comprehensive webmail interface detection.
|
||||
if ( rec?$agent && rec?$path )
|
||||
if ( rec?$agent && rec?$received_from_originating_ip )
|
||||
{
|
||||
local s = Software::parse(rec$agent, rec$path[|rec$path|], MAIL_CLIENT);
|
||||
local s = Software::parse(rec$agent, rec$received_from_originating_ip, MAIL_CLIENT);
|
||||
Software::found(rec$id, s);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,365 +1,2 @@
|
|||
## This script provides the framework for software version detection and
|
||||
## parsing, but doesn't actually do any detection on it's own. It relys on
|
||||
## other protocol specific scripts to parse out software from the protocol(s)
|
||||
## that they analyze. The entry point for providing new software detections
|
||||
## to this framework is through the Software::found function.
|
||||
|
||||
@load functions
|
||||
@load notice
|
||||
|
||||
module Software;
|
||||
|
||||
redef enum Notice::Type += {
|
||||
## For certain softwares, a version changing may matter. In that case,
|
||||
## this notice will be generated. Software that matters if the version
|
||||
## changes can be configured with the
|
||||
## Software::interesting_version_changes variable.
|
||||
Software_Version_Change,
|
||||
};
|
||||
|
||||
redef enum Log::ID += { SOFTWARE };
|
||||
|
||||
export {
|
||||
type Type: enum {
|
||||
UNKNOWN,
|
||||
OPERATING_SYSTEM,
|
||||
WEB_APPLICATION,
|
||||
FTP_SERVER,
|
||||
FTP_CLIENT,
|
||||
DATABASE_SERVER,
|
||||
## There are a number of ways to detect printers on the network.
|
||||
PRINTER,
|
||||
};
|
||||
|
||||
type Version: record {
|
||||
major: count &optional; ##< Major version number
|
||||
minor: count &optional; ##< Minor version number
|
||||
minor2: count &optional; ##< Minor subversion number
|
||||
addl: string &optional; ##< Additional version string (e.g. "beta42")
|
||||
} &log;
|
||||
|
||||
type Info: record {
|
||||
## The time at which the software was first detected.
|
||||
ts: time &log;
|
||||
## The IP address detected running the software.
|
||||
host: addr &log;
|
||||
## The type of software detected (e.g. WEB_SERVER)
|
||||
software_type: Type &log &default=UNKNOWN;
|
||||
## Name of the software (e.g. Apache)
|
||||
name: string &log;
|
||||
## Version of the software
|
||||
version: Version &log;
|
||||
## The full unparsed version string found because the version parsing
|
||||
## doesn't work 100% reliably and this acts as a fall back in the logs.
|
||||
unparsed_version: string &log &optional;
|
||||
};
|
||||
|
||||
## The hosts whose software should be logged.
|
||||
## Choices are: LocalHosts, RemoteHosts, Enabled, Disabled
|
||||
const logging = Enabled &redef;
|
||||
|
||||
## In case you are interested in more than logging just local assets
|
||||
## you can split the log file.
|
||||
#const split_log_file = F &redef;
|
||||
|
||||
## Some software is more interesting when the version changes. This is
|
||||
## a set of all software that should raise a notice when a different version
|
||||
## is seen.
|
||||
const interesting_version_changes: set[string] = {
|
||||
"SSH"
|
||||
} &redef;
|
||||
|
||||
## Other scripts should call this function when they detect software.
|
||||
## @param unparsed_version: This is the full string from which the
|
||||
## Software::Info was extracted.
|
||||
## @return: T if the software was logged, F otherwise.
|
||||
global found: function(id: conn_id, info: Software::Info): bool;
|
||||
|
||||
## This function can take many software version strings and parse them into
|
||||
## a sensible Software::Version record. There are still many cases where
|
||||
## scripts may have to have their own specific version parsing though.
|
||||
global parse: function(unparsed_version: string,
|
||||
host: addr,
|
||||
software_type: Type): Info;
|
||||
|
||||
## Compare two versions.
|
||||
## @return: Returns -1 for v1 < v2, 0 for v1 == v2, 1 for v1 > v2.
|
||||
## If the numerical version numbers match, the addl string
|
||||
## is compared lexicographically.
|
||||
global cmp_versions: function(v1: Version, v2: Version): int;
|
||||
|
||||
## Index is the name of the software.
|
||||
type SoftwareSet: table[string] of Info;
|
||||
# The set of software associated with an address.
|
||||
global tracked_software: table[addr] of SoftwareSet &create_expire=1day &synchronized;
|
||||
|
||||
global log_software: event(rec: Info);
|
||||
}
|
||||
|
||||
event bro_init()
|
||||
{
|
||||
Log::create_stream(SOFTWARE, [$columns=Software::Info, $ev=log_software]);
|
||||
}
|
||||
|
||||
function parse_mozilla(unparsed_version: string,
|
||||
host: addr,
|
||||
software_type: Type): Info
|
||||
{
|
||||
local software_name = "<parse error>";
|
||||
local v: Version;
|
||||
local parts: table[count] of string;
|
||||
|
||||
if ( /MSIE 7.*Trident\/4\.0/ in unparsed_version )
|
||||
{
|
||||
software_name = "MSIE";
|
||||
v = [$major=8,$minor=0];
|
||||
}
|
||||
else if ( /[cC]ompatible; MSIE [0-9\.]*/ in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /MSIE \/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
return parse(parts[2], host, software_type);
|
||||
}
|
||||
else if ( /Version\/.*Safari\// in unparsed_version )
|
||||
{
|
||||
software_name = "Safari";
|
||||
parts = split_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
{
|
||||
v = parse(parts[2], host, software_type)$version;
|
||||
if ( / Mobile\// in unparsed_version )
|
||||
v$addl = "Mobile";
|
||||
}
|
||||
}
|
||||
else if ( /Firefox\/[0-9\.]*/ in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /Firefox\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
return parse(parts[2], host, software_type);
|
||||
}
|
||||
else if ( /Chrome\/.*Safari\// in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /Chrome\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
return parse(parts[2], host, software_type);
|
||||
}
|
||||
else if ( /^Opera\// in unparsed_version )
|
||||
{
|
||||
software_name = "Opera";
|
||||
parts = split_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2], host, software_type)$version;
|
||||
}
|
||||
else if ( /Thunderbird\/[0-9\.]*/ in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /Thunderbird\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
return parse(parts[2], host, software_type);
|
||||
}
|
||||
|
||||
return [$ts=network_time(), $host=host, $name=software_name, $version=v,
|
||||
$unparsed_version=unparsed_version];
|
||||
}
|
||||
|
||||
# Don't even try to understand this now, just make sure the tests are
|
||||
# working.
|
||||
function parse(unparsed_version: string,
|
||||
host: addr,
|
||||
software_type: Type): Info
|
||||
{
|
||||
local software_name = "<parse error>";
|
||||
local v: Version;
|
||||
|
||||
# Parse browser-alike versions separately
|
||||
if ( /^(Mozilla|Opera)\/[0-9]\./ in unparsed_version )
|
||||
{
|
||||
return parse_mozilla(unparsed_version, host, software_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
# The regular expression should match the complete version number
|
||||
# and software name.
|
||||
local version_parts = split_n(unparsed_version, /\/?( [\(])?v?[0-9\-\._, ]{2,}/, T, 1);
|
||||
if ( 1 in version_parts )
|
||||
software_name = strip(version_parts[1]);
|
||||
if ( |version_parts| >= 2 )
|
||||
{
|
||||
# Remove the name/version separator if it's left at the beginning
|
||||
# of the version number from the previous split_all.
|
||||
local sv = strip(version_parts[2]);
|
||||
if ( /^[\/\-\._v\(]/ in sv )
|
||||
sv = strip(sub(version_parts[2], /^\(?[\/\-\._v]/, ""));
|
||||
local version_numbers = split_n(sv, /[\-\._,\[\(\{ ]/, F, 3);
|
||||
if ( 4 in version_numbers && version_numbers[4] != "" )
|
||||
v$addl = strip(version_numbers[4]);
|
||||
else if ( 3 in version_parts && version_parts[3] != "" )
|
||||
{
|
||||
if ( /^[[:blank:]]*\([a-zA-Z0-9\-\._[:blank:]]*\)/ in version_parts[3] )
|
||||
{
|
||||
v$addl = split_n(version_parts[3], /[\(\)]/, F, 2)[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
local vp = split_n(version_parts[3], /[\-\._,;\[\]\(\)\{\} ]/, F, 3);
|
||||
if ( |vp| >= 1 && vp[1] != "" )
|
||||
{
|
||||
v$addl = strip(vp[1]);
|
||||
}
|
||||
else if ( |vp| >= 2 && vp[2] != "" )
|
||||
{
|
||||
v$addl = strip(vp[2]);
|
||||
}
|
||||
else if ( |vp| >= 3 && vp[3] != "" )
|
||||
{
|
||||
v$addl = strip(vp[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
v$addl = strip(version_parts[3]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ( |version_numbers| >= 3 && version_numbers[3] != "" )
|
||||
v$minor2 = to_count(version_numbers[3]);
|
||||
if ( |version_numbers| >= 2 && version_numbers[2] != "" )
|
||||
v$minor = to_count(version_numbers[2]);
|
||||
if ( |version_numbers| >= 1 && version_numbers[1] != "" )
|
||||
v$major = to_count(version_numbers[1]);
|
||||
}
|
||||
}
|
||||
return [$ts=network_time(), $host=host, $name=software_name,
|
||||
$version=v, $unparsed_version=unparsed_version,
|
||||
$software_type=software_type];
|
||||
}
|
||||
|
||||
|
||||
function cmp_versions(v1: Version, v2: Version): int
|
||||
{
|
||||
if ( v1?$major && v2?$major )
|
||||
{
|
||||
if ( v1$major < v2$major )
|
||||
return -1;
|
||||
if ( v1$major > v2$major )
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !v1?$major && !v2?$major )
|
||||
{ }
|
||||
else
|
||||
return v1?$major ? 1 : -1;
|
||||
}
|
||||
|
||||
if ( v1?$minor && v2?$minor )
|
||||
{
|
||||
if ( v1$minor < v2$minor )
|
||||
return -1;
|
||||
if ( v1$minor > v2$minor )
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !v1?$minor && !v2?$minor )
|
||||
{ }
|
||||
else
|
||||
return v1?$minor ? 1 : -1;
|
||||
}
|
||||
|
||||
if ( v1?$minor2 && v2?$minor2 )
|
||||
{
|
||||
if ( v1$minor2 < v2$minor2 )
|
||||
return -1;
|
||||
if ( v1$minor2 > v2$minor2 )
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !v1?$minor2 && !v2?$minor2 )
|
||||
{ }
|
||||
else
|
||||
return v1?$minor2 ? 1 : -1;
|
||||
}
|
||||
|
||||
if ( v1?$addl && v2?$addl )
|
||||
return strcmp(v1$addl, v2$addl);
|
||||
else
|
||||
{
|
||||
if ( !v1?$addl && !v2?$addl )
|
||||
return 0;
|
||||
else
|
||||
return v1?$addl ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
function software_endpoint_name(id: conn_id, host: addr): string
|
||||
{
|
||||
return fmt("%s %s", host, (host == id$orig_h ? "client" : "server"));
|
||||
}
|
||||
|
||||
# Convert a version into a string "a.b.c-x".
|
||||
function software_fmt_version(v: Version): string
|
||||
{
|
||||
return fmt("%d.%d.%d%s",
|
||||
v?$major ? v$major : 0,
|
||||
v?$minor ? v$minor : 0,
|
||||
v?$minor2 ? v$minor2 : 0,
|
||||
v?$addl ? fmt("-%s", v$addl) : "");
|
||||
}
|
||||
|
||||
# Convert a software into a string "name a.b.cx".
|
||||
function software_fmt(i: Info): string
|
||||
{
|
||||
return fmt("%s %s", i$name, software_fmt_version(i$version));
|
||||
}
|
||||
|
||||
# Insert a mapping into the table
|
||||
# Overides old entries for the same software and generates events if needed.
|
||||
event software_register(id: conn_id, info: Info)
|
||||
{
|
||||
# Host already known?
|
||||
if ( info$host !in tracked_software )
|
||||
tracked_software[info$host] = table();
|
||||
|
||||
local ts = tracked_software[info$host];
|
||||
# Software already registered for this host?
|
||||
if ( info$name in ts )
|
||||
{
|
||||
local old = ts[info$name];
|
||||
|
||||
# Is it a potentially interesting version change
|
||||
# and is it a different version?
|
||||
if ( info$name in interesting_version_changes &&
|
||||
cmp_versions(old$version, info$version) != 0 )
|
||||
{
|
||||
local msg = fmt("%.6f %s switched from %s to %s (%s)",
|
||||
network_time(), software_endpoint_name(id, info$host),
|
||||
software_fmt_version(old$version),
|
||||
software_fmt(info), info$software_type);
|
||||
NOTICE([$note=Software_Version_Change, $id=id,
|
||||
$msg=msg, $sub=software_fmt(info)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
# If the software is known to be on this host already and version
|
||||
# changes either aren't interesting or it's the same version as
|
||||
# already known, just return and don't log.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log::write(SOFTWARE, info);
|
||||
ts[info$name] = info;
|
||||
}
|
||||
|
||||
function found(id: conn_id, info: Info): bool
|
||||
{
|
||||
if ( addr_matches_hosts(info$host, logging) )
|
||||
{
|
||||
event software_register(id, info);
|
||||
return T;
|
||||
}
|
||||
else
|
||||
return F;
|
||||
}
|
||||
@load software/base
|
||||
@load software/vulnerable
|
390
policy/software/base.bro
Normal file
390
policy/software/base.bro
Normal file
|
@ -0,0 +1,390 @@
|
|||
## This script provides the framework for software version detection and
|
||||
## parsing, but doesn't actually do any detection on it's own. It relys on
|
||||
## other protocol specific scripts to parse out software from the protocol(s)
|
||||
## that they analyze. The entry point for providing new software detections
|
||||
## to this framework is through the Software::found function.
|
||||
|
||||
@load functions
|
||||
@load notice
|
||||
|
||||
|
||||
module Software;
|
||||
|
||||
redef enum Notice::Type += {
|
||||
## For certain softwares, a version changing may matter. In that case,
|
||||
## this notice will be generated. Software that matters if the version
|
||||
## changes can be configured with the
|
||||
## Software::interesting_version_changes variable.
|
||||
Software_Version_Change,
|
||||
};
|
||||
|
||||
redef enum Log::ID += { SOFTWARE };
|
||||
|
||||
export {
|
||||
type Type: enum {
|
||||
UNKNOWN,
|
||||
OPERATING_SYSTEM,
|
||||
WEB_APPLICATION,
|
||||
FTP_SERVER,
|
||||
FTP_CLIENT,
|
||||
DATABASE_SERVER,
|
||||
## There are a number of ways to detect printers on the network.
|
||||
PRINTER,
|
||||
};
|
||||
|
||||
type Version: record {
|
||||
major: count &optional; ##< Major version number
|
||||
minor: count &optional; ##< Minor version number
|
||||
minor2: count &optional; ##< Minor subversion number
|
||||
addl: string &optional; ##< Additional version string (e.g. "beta42")
|
||||
} &log;
|
||||
|
||||
type Info: record {
|
||||
## The time at which the software was first detected.
|
||||
ts: time &log;
|
||||
## The IP address detected running the software.
|
||||
host: addr &log;
|
||||
## The type of software detected (e.g. WEB_SERVER)
|
||||
software_type: Type &log &default=UNKNOWN;
|
||||
## Name of the software (e.g. Apache)
|
||||
name: string &log;
|
||||
## Version of the software
|
||||
version: Version &log;
|
||||
## The full unparsed version string found because the version parsing
|
||||
## doesn't work 100% reliably and this acts as a fall back in the logs.
|
||||
unparsed_version: string &log &optional;
|
||||
};
|
||||
|
||||
## The hosts whose software should be logged.
|
||||
## Choices are: LocalHosts, RemoteHosts, Enabled, Disabled
|
||||
const logging = Enabled &redef;
|
||||
|
||||
## In case you are interested in more than logging just local assets
|
||||
## you can split the log file.
|
||||
#const split_log_file = F &redef;
|
||||
|
||||
## Some software is more interesting when the version changes. This is
|
||||
## a set of all software that should raise a notice when a different version
|
||||
## is seen.
|
||||
const interesting_version_changes: set[string] = {
|
||||
"SSH"
|
||||
} &redef;
|
||||
|
||||
## Other scripts should call this function when they detect software.
|
||||
## @param unparsed_version: This is the full string from which the
|
||||
## Software::Info was extracted.
|
||||
## @return: T if the software was logged, F otherwise.
|
||||
global found: function(id: conn_id, info: Software::Info): bool;
|
||||
|
||||
## This function can take many software version strings and parse them into
|
||||
## a sensible Software::Version record. There are still many cases where
|
||||
## scripts may have to have their own specific version parsing though.
|
||||
global parse: function(unparsed_version: string,
|
||||
host: addr,
|
||||
software_type: Type): Info;
|
||||
|
||||
## Compare two versions.
|
||||
## @return: Returns -1 for v1 < v2, 0 for v1 == v2, 1 for v1 > v2.
|
||||
## If the numerical version numbers match, the addl string
|
||||
## is compared lexicographically.
|
||||
global cmp_versions: function(v1: Version, v2: Version): int;
|
||||
|
||||
## Index is the name of the software.
|
||||
type SoftwareSet: table[string] of Info;
|
||||
# The set of software associated with an address.
|
||||
global tracked_software: table[addr] of SoftwareSet &create_expire=1day &synchronized;
|
||||
|
||||
global log_software: event(rec: Info);
|
||||
}
|
||||
|
||||
event bro_init()
|
||||
{
|
||||
Log::create_stream(SOFTWARE, [$columns=Info, $ev=log_software]);
|
||||
}
|
||||
|
||||
function parse_mozilla(unparsed_version: string,
|
||||
host: addr,
|
||||
software_type: Type): Info
|
||||
{
|
||||
local software_name = "<unknown browser>";
|
||||
local v: Version;
|
||||
local parts: table[count] of string;
|
||||
|
||||
if ( /Opera [0-9\.]*$/ in unparsed_version )
|
||||
{
|
||||
software_name = "Opera";
|
||||
parts = split_all(unparsed_version, /Opera [0-9\.]*$/);
|
||||
if ( 2 in parts )
|
||||
return parse(parts[2], host, software_type);
|
||||
}
|
||||
else if ( /MSIE 7.*Trident\/4\.0/ in unparsed_version )
|
||||
{
|
||||
software_name = "MSIE";
|
||||
v = [$major=8,$minor=0];
|
||||
}
|
||||
else if ( /[cC]ompatible; MSIE [0-9\.]*/ in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /MSIE [0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
return parse(parts[2], host, software_type);
|
||||
}
|
||||
else if ( /Version\/.*Safari\// in unparsed_version )
|
||||
{
|
||||
software_name = "Safari";
|
||||
parts = split_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
{
|
||||
v = parse(parts[2], host, software_type)$version;
|
||||
if ( / Mobile\/?.* Safari/ in unparsed_version )
|
||||
v$addl = "Mobile";
|
||||
}
|
||||
}
|
||||
else if ( /Firefox\/[0-9\.]*/ in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /Firefox\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
return parse(parts[2], host, software_type);
|
||||
}
|
||||
else if ( /Chrome\/.*Safari\// in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /Chrome\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
return parse(parts[2], host, software_type);
|
||||
}
|
||||
else if ( /^Opera\// in unparsed_version )
|
||||
{
|
||||
if ( /Opera M(ini|obi)\// in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /Opera M(ini|obi)/);
|
||||
software_name = parts[2];
|
||||
parts = split_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2], host, software_type)$version;
|
||||
else
|
||||
{
|
||||
parts = split_all(unparsed_version, /Opera Mini\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2], host, software_type)$version;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
software_name = "Opera";
|
||||
parts = split_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2], host, software_type)$version;
|
||||
}
|
||||
}
|
||||
else if ( /Thunderbird\/[0-9\.]*/ in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /Thunderbird\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
return parse(parts[2], host, software_type);
|
||||
}
|
||||
|
||||
return [$ts=network_time(), $host=host, $name=software_name, $version=v,
|
||||
$software_type=software_type, $unparsed_version=unparsed_version];
|
||||
}
|
||||
|
||||
# Don't even try to understand this now, just make sure the tests are
|
||||
# working.
|
||||
function parse(unparsed_version: string,
|
||||
host: addr,
|
||||
software_type: Type): Info
|
||||
{
|
||||
local software_name = "<parse error>";
|
||||
local v: Version;
|
||||
|
||||
# Parse browser-alike versions separately
|
||||
if ( /^(Mozilla|Opera)\/[0-9]\./ in unparsed_version )
|
||||
{
|
||||
return parse_mozilla(unparsed_version, host, software_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
# The regular expression should match the complete version number
|
||||
# and software name.
|
||||
local version_parts = split_n(unparsed_version, /\/?( [\(])?v?[0-9\-\._, ]{2,}/, T, 1);
|
||||
if ( 1 in version_parts )
|
||||
software_name = strip(version_parts[1]);
|
||||
if ( |version_parts| >= 2 )
|
||||
{
|
||||
# Remove the name/version separator if it's left at the beginning
|
||||
# of the version number from the previous split_all.
|
||||
local sv = strip(version_parts[2]);
|
||||
if ( /^[\/\-\._v\(]/ in sv )
|
||||
sv = strip(sub(version_parts[2], /^\(?[\/\-\._v]/, ""));
|
||||
local version_numbers = split_n(sv, /[\-\._,\[\(\{ ]/, F, 3);
|
||||
if ( 4 in version_numbers && version_numbers[4] != "" )
|
||||
v$addl = strip(version_numbers[4]);
|
||||
else if ( 3 in version_parts && version_parts[3] != "" )
|
||||
{
|
||||
if ( /^[[:blank:]]*\([a-zA-Z0-9\-\._[:blank:]]*\)/ in version_parts[3] )
|
||||
{
|
||||
v$addl = split_n(version_parts[3], /[\(\)]/, F, 2)[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
local vp = split_n(version_parts[3], /[\-\._,;\[\]\(\)\{\} ]/, F, 3);
|
||||
if ( |vp| >= 1 && vp[1] != "" )
|
||||
{
|
||||
v$addl = strip(vp[1]);
|
||||
}
|
||||
else if ( |vp| >= 2 && vp[2] != "" )
|
||||
{
|
||||
v$addl = strip(vp[2]);
|
||||
}
|
||||
else if ( |vp| >= 3 && vp[3] != "" )
|
||||
{
|
||||
v$addl = strip(vp[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
v$addl = strip(version_parts[3]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ( |version_numbers| >= 3 && version_numbers[3] != "" )
|
||||
v$minor2 = to_count(version_numbers[3]);
|
||||
if ( |version_numbers| >= 2 && version_numbers[2] != "" )
|
||||
v$minor = to_count(version_numbers[2]);
|
||||
if ( |version_numbers| >= 1 && version_numbers[1] != "" )
|
||||
v$major = to_count(version_numbers[1]);
|
||||
}
|
||||
}
|
||||
return [$ts=network_time(), $host=host, $name=software_name,
|
||||
$version=v, $unparsed_version=unparsed_version,
|
||||
$software_type=software_type];
|
||||
}
|
||||
|
||||
|
||||
function cmp_versions(v1: Version, v2: Version): int
|
||||
{
|
||||
if ( v1?$major && v2?$major )
|
||||
{
|
||||
if ( v1$major < v2$major )
|
||||
return -1;
|
||||
if ( v1$major > v2$major )
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !v1?$major && !v2?$major )
|
||||
{ }
|
||||
else
|
||||
return v1?$major ? 1 : -1;
|
||||
}
|
||||
|
||||
if ( v1?$minor && v2?$minor )
|
||||
{
|
||||
if ( v1$minor < v2$minor )
|
||||
return -1;
|
||||
if ( v1$minor > v2$minor )
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !v1?$minor && !v2?$minor )
|
||||
{ }
|
||||
else
|
||||
return v1?$minor ? 1 : -1;
|
||||
}
|
||||
|
||||
if ( v1?$minor2 && v2?$minor2 )
|
||||
{
|
||||
if ( v1$minor2 < v2$minor2 )
|
||||
return -1;
|
||||
if ( v1$minor2 > v2$minor2 )
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !v1?$minor2 && !v2?$minor2 )
|
||||
{ }
|
||||
else
|
||||
return v1?$minor2 ? 1 : -1;
|
||||
}
|
||||
|
||||
if ( v1?$addl && v2?$addl )
|
||||
return strcmp(v1$addl, v2$addl);
|
||||
else
|
||||
{
|
||||
if ( !v1?$addl && !v2?$addl )
|
||||
return 0;
|
||||
else
|
||||
return v1?$addl ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
function software_endpoint_name(id: conn_id, host: addr): string
|
||||
{
|
||||
return fmt("%s %s", host, (host == id$orig_h ? "client" : "server"));
|
||||
}
|
||||
|
||||
# Convert a version into a string "a.b.c-x".
|
||||
function software_fmt_version(v: Version): string
|
||||
{
|
||||
return fmt("%d.%d.%d%s",
|
||||
v?$major ? v$major : 0,
|
||||
v?$minor ? v$minor : 0,
|
||||
v?$minor2 ? v$minor2 : 0,
|
||||
v?$addl ? fmt("-%s", v$addl) : "");
|
||||
}
|
||||
|
||||
# Convert a software into a string "name a.b.cx".
|
||||
function software_fmt(i: Info): string
|
||||
{
|
||||
return fmt("%s %s", i$name, software_fmt_version(i$version));
|
||||
}
|
||||
|
||||
# Insert a mapping into the table
|
||||
# Overides old entries for the same software and generates events if needed.
|
||||
event software_register(id: conn_id, info: Info)
|
||||
{
|
||||
# Host already known?
|
||||
if ( info$host !in tracked_software )
|
||||
tracked_software[info$host] = table();
|
||||
|
||||
local ts = tracked_software[info$host];
|
||||
# Software already registered for this host?
|
||||
if ( info$name in ts )
|
||||
{
|
||||
local old = ts[info$name];
|
||||
|
||||
# Is it a potentially interesting version change
|
||||
# and is it a different version?
|
||||
if ( info$name in interesting_version_changes &&
|
||||
cmp_versions(old$version, info$version) != 0 )
|
||||
{
|
||||
local msg = fmt("%.6f %s switched from %s to %s (%s)",
|
||||
network_time(), software_endpoint_name(id, info$host),
|
||||
software_fmt_version(old$version),
|
||||
software_fmt(info), info$software_type);
|
||||
NOTICE([$note=Software_Version_Change, $id=id,
|
||||
$msg=msg, $sub=software_fmt(info)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
# If the software is known to be on this host already and version
|
||||
# changes either aren't interesting or it's the same version as
|
||||
# already known, just return and don't log.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log::write(SOFTWARE, info);
|
||||
ts[info$name] = info;
|
||||
}
|
||||
|
||||
function found(id: conn_id, info: Info): bool
|
||||
{
|
||||
if ( addr_matches_hosts(info$host, logging) )
|
||||
{
|
||||
event software_register(id, info);
|
||||
return T;
|
||||
}
|
||||
else
|
||||
return F;
|
||||
}
|
29
policy/software/vulnerable.bro
Normal file
29
policy/software/vulnerable.bro
Normal file
|
@ -0,0 +1,29 @@
|
|||
@load software/base
|
||||
@load notice
|
||||
|
||||
module Software;
|
||||
|
||||
redef enum Notice::Type += {
|
||||
VULNERABLE,
|
||||
};
|
||||
|
||||
export {
|
||||
## This is a table of software versions indexed by the name of the software
|
||||
## and yielding the latest version that is vulnerable.
|
||||
const vulnerable_versions: table[string] of Version &redef;
|
||||
}
|
||||
|
||||
redef vulnerable_versions += {
|
||||
["Flash"] = [$major=10,$minor=2,$minor2=153,$addl="1"],
|
||||
["Java"] = [$major=1,$minor=6,$minor2=0,$addl="22"],
|
||||
};
|
||||
|
||||
event log_software(rec: Info)
|
||||
{
|
||||
if ( rec$name in vulnerable_versions &&
|
||||
cmp_versions(rec$version, vulnerable_versions[rec$name]) < 1 )
|
||||
{
|
||||
print fmt("VULNERABLE %s", software_fmt(rec));
|
||||
NOTICE([$note=VULNERABLE, $src=rec$host, $msg=software_fmt(rec)]);
|
||||
}
|
||||
}
|
|
@ -47,6 +47,9 @@ global matched_software: table[string] of Software::Info = {
|
|||
[$name="CacheFlyServe", $version=[$major=26,$addl="b"], $host=0.0.0.0, $ts=ts],
|
||||
["Apache/2.0.46 (Win32) mod_ssl/2.0.46 OpenSSL/0.9.7b mod_jk2/2.0.4"] =
|
||||
[$name="Apache", $version=[$major=2,$minor=0,$minor2=46,$addl="Win32"], $host=0.0.0.0, $ts=ts],
|
||||
# I have no clue how I'd support this without a special case.
|
||||
#["Apache mod_fcgid/2.3.6 mod_auth_passthrough/2.1 mod_bwlimited/1.4 FrontPage/5.0.2.2635"] =
|
||||
# [$name="Apache", $version=[], $host=0.0.0.0, $ts=ts],
|
||||
["Apple iPhone v4.3.1 Weather v1.0.0.8G4"] =
|
||||
[$name="Apple iPhone", $version=[$major=4,$minor=3,$minor2=1,$addl="Weather"], $host=0.0.0.0, $ts=ts],
|
||||
["Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_2 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8H7 Safari/6533.18.5"] =
|
||||
|
@ -59,7 +62,29 @@ global matched_software: table[string] of Software::Info = {
|
|||
[$name="Thunderbird", $version=[$major=3,$minor=1,$minor2=5], $host=0.0.0.0, $ts=ts],
|
||||
["iTunes/9.0 (Macintosh; Intel Mac OS X 10.5.8) AppleWebKit/531.9"] =
|
||||
[$name="iTunes", $version=[$major=9,$minor=0,$addl="Macintosh"], $host=0.0.0.0, $ts=ts],
|
||||
["Java1.3.1_04"] =
|
||||
[$name="Java", $version=[$major=1,$minor=3,$minor2=1,$addl="04"], $host=0.0.0.0, $ts=ts],
|
||||
["Mozilla/5.0 (Linux; U; Android 2.3.3; zh-tw; HTC Pyramid Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"] =
|
||||
[$name="Safari", $version=[$major=4,$minor=0,$addl="Mobile"], $host=0.0.0.0, $ts=ts],
|
||||
["Opera/9.80 (J2ME/MIDP; Opera Mini/9.80 (S60; SymbOS; Opera Mobi/23.348; U; en) Presto/2.5.25 Version/10.54"] =
|
||||
[$name="Opera Mini", $version=[$major=10,$minor=54], $host=0.0.0.0, $ts=ts],
|
||||
["Opera/9.80 (J2ME/MIDP; Opera Mini/5.0.18741/18.794; U; en) Presto/2.4.15"] =
|
||||
[$name="Opera Mini", $version=[$major=5,$minor=0,$minor2=18741], $host=0.0.0.0, $ts=ts],
|
||||
["Opera/9.80 (Windows NT 5.1; Opera Mobi/49; U; en) Presto/2.4.18 Version/10.00"] =
|
||||
[$name="Opera Mobi", $version=[$major=10,$minor=0], $host=0.0.0.0, $ts=ts],
|
||||
["Mozilla/4.0 (compatible; MSIE 8.0; Android 2.2.2; Linux; Opera Mobi/ADR-1103311355; en) Opera 11.00"] =
|
||||
[$name="Opera", $version=[$major=11,$minor=0], $host=0.0.0.0, $ts=ts],
|
||||
["Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; GTB5; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; InfoPath.2)"] =
|
||||
[$name="MSIE", $version=[$major=7,$minor=0], $host=0.0.0.0, $ts=ts],
|
||||
|
||||
|
||||
# This is an FTP client (found with CLNT command)
|
||||
["Total Commander"] =
|
||||
[$name="Total Commander", $version=[], $host=0.0.0.0, $ts=ts],
|
||||
#["(vsFTPd 2.0.5)"] =
|
||||
# [$name="vsFTPd", $version=[$major=2,$minor=0,$minor2=5], $host=0.0.0.0, $ts=ts],
|
||||
|
||||
|
||||
};
|
||||
|
||||
event bro_init()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue