diff --git a/policy/dpd/packet-segment-logging.bro b/policy/dpd/packet-segment-logging.bro index c3fc5c5e19..3d83203478 100644 --- a/policy/dpd/packet-segment-logging.bro +++ b/policy/dpd/packet-segment-logging.bro @@ -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 diff --git a/policy/ftp/base.bro b/policy/ftp/base.bro index 773fbd556c..a66d790007 100644 --- a/policy/ftp/base.bro +++ b/policy/ftp/base.bro @@ -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]; diff --git a/policy/ftp/file-extract.bro b/policy/ftp/file-extract.bro index 421b8b9c8d..37e8907db6 100644 --- a/policy/ftp/file-extract.bro +++ b/policy/ftp/file-extract.bro @@ -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; + } \ No newline at end of file diff --git a/policy/http/base.bro b/policy/http/base.bro index 95e98e32dd..52d8087ffb 100644 --- a/policy/http/base.bro +++ b/policy/http/base.bro @@ -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); } } diff --git a/policy/notice.bro b/policy/notice.bro index 9a3b12adf3..8ddca98771 100644 --- a/policy/notice.bro +++ b/policy/notice.bro @@ -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 diff --git a/policy/smtp/software.bro b/policy/smtp/software.bro index 290e9dadaf..0549b4866b 100644 --- a/policy/smtp/software.bro +++ b/policy/smtp/software.bro @@ -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); } } diff --git a/policy/software.bro b/policy/software.bro index 43252a0772..3a79178558 100644 --- a/policy/software.bro +++ b/policy/software.bro @@ -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 = ""; - 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 = ""; - 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 \ No newline at end of file diff --git a/policy/software/base.bro b/policy/software/base.bro new file mode 100644 index 0000000000..69cadbcbfa --- /dev/null +++ b/policy/software/base.bro @@ -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 = ""; + 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 = ""; + 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; + } diff --git a/policy/software/vulnerable.bro b/policy/software/vulnerable.bro new file mode 100644 index 0000000000..35ed7b80b4 --- /dev/null +++ b/policy/software/vulnerable.bro @@ -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)]); + } + } \ No newline at end of file diff --git a/testing/btest/policy/software-known-version-parsing.bro b/testing/btest/policy/software-known-version-parsing.bro index 2bd9664c32..db60694f39 100644 --- a/testing/btest/policy/software-known-version-parsing.bro +++ b/testing/btest/policy/software-known-version-parsing.bro @@ -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()