Merge remote-tracking branch 'origin/master' into topic/seth/file-entropy

This commit is contained in:
Seth Hall 2015-01-30 00:52:41 -05:00
commit 8e53e719f3
1894 changed files with 189157 additions and 279280 deletions

View file

@ -1,8 +1,9 @@
##! This is a utility script that implements the controller interface for the
##! control framework. It's intended to be run to control a remote Bro
##! control framework. It's intended to be run to control a remote Bro
##! and then shutdown.
##!
##! It's intended to be used from the command line like this::
##!
##! bro <scripts> frameworks/control/controller Control::host=<host_addr> Control::port=<host_port> Control::cmd=<command> [Control::arg=<arg>]
@load base/frameworks/control

View file

@ -1,6 +1,6 @@
##! 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.
##! 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 :bro:see:`DPD::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.
@ -10,8 +10,8 @@ module DPD;
export {
redef record Info += {
## A chunk of the payload the most likely resulted in the protocol
## violation.
## A chunk of the payload that most likely resulted in the
## protocol violation.
packet_segment: string &optional &log;
};

View file

@ -23,35 +23,50 @@ export {
/application\/jar/ |
/video\/mp4/ &redef;
## The malware hash registry runs each malware sample through several A/V engines.
## Team Cymru returns a percentage to indicate how many A/V engines flagged the
## sample as malicious. This threshold allows you to require a minimum detection
## rate.
## The Match notice has a sub message with a URL where you can get more
## information about the file. The %s will be replaced with the SHA-1
## hash of the file.
const match_sub_url = "https://www.virustotal.com/en/search/?query=%s" &redef;
## The malware hash registry runs each malware sample through several
## A/V engines. Team Cymru returns a percentage to indicate how
## many A/V engines flagged the sample as malicious. This threshold
## allows you to require a minimum detection rate.
const notice_threshold = 10 &redef;
}
event file_hash(f: fa_file, kind: string, hash: string)
function do_mhr_lookup(hash: string, fi: Notice::FileInfo)
{
if ( kind=="sha1" && match_file_types in f$mime_type )
local hash_domain = fmt("%s.malware.hash.cymru.com", hash);
when ( local MHR_result = lookup_hostname_txt(hash_domain) )
{
local hash_domain = fmt("%s.malware.hash.cymru.com", hash);
when ( local MHR_result = lookup_hostname_txt(hash_domain) )
# Data is returned as "<dateFirstDetected> <detectionRate>"
local MHR_answer = split1(MHR_result, / /);
if ( |MHR_answer| == 2 )
{
# Data is returned as "<dateFirstDetected> <detectionRate>"
local MHR_answer = split1(MHR_result, / /);
if ( |MHR_answer| == 2 )
local mhr_detect_rate = to_count(MHR_answer[2]);
if ( mhr_detect_rate >= notice_threshold )
{
local mhr_first_detected = double_to_time(to_double(MHR_answer[1]));
local mhr_detect_rate = to_count(MHR_answer[2]);
local readable_first_detected = strftime("%Y-%m-%d %H:%M:%S", mhr_first_detected);
if ( mhr_detect_rate >= notice_threshold )
{
local message = fmt("Malware Hash Registry Detection rate: %d%% Last seen: %s", mhr_detect_rate, readable_first_detected);
local virustotal_url = fmt("https://www.virustotal.com/en/file/%s/analysis/", hash);
NOTICE([$note=Match, $msg=message, $sub=virustotal_url, $f=f]);
}
local message = fmt("Malware Hash Registry Detection rate: %d%% Last seen: %s", mhr_detect_rate, readable_first_detected);
local virustotal_url = fmt(match_sub_url, hash);
# We don't have the full fa_file record here in order to
# avoid the "when" statement cloning it (expensive!).
local n: Notice::Info = Notice::Info($note=Match, $msg=message, $sub=virustotal_url);
Notice::populate_file_info2(fi, n);
NOTICE(n);
}
}
}
}
event file_hash(f: fa_file, kind: string, hash: string)
{
if ( kind == "sha1" && f?$info && f$info?$mime_type &&
match_file_types in f$info$mime_type )
do_mhr_lookup(hash, Notice::create_file_info(f));
}

View file

@ -1,4 +1,4 @@
# Perform MD5 and SHA1 hashing on all files.
##! Perform MD5 and SHA1 hashing on all files.
event file_new(f: fa_file)
{

View file

@ -18,7 +18,7 @@ export {
do_notice: bool &default=F;
## Restrictions on when notices are created to only create
## them if the do_notice field is T and the notice was
## them if the *do_notice* field is T and the notice was
## seen in the indicated location.
if_in: Intel::Where &optional;
};

View file

@ -0,0 +1 @@
Scripts that send data to the intelligence framework.

View file

@ -1,8 +1,10 @@
@load ./conn-established
@load ./dns
@load ./http-host-header
@load ./file-hashes
@load ./file-names
@load ./http-headers
@load ./http-url
@load ./http-user-agents
@load ./ssl
@load ./smtp
@load ./smtp-url-extraction
@load ./smtp-url-extraction
@load ./x509

View file

@ -0,0 +1,12 @@
@load base/frameworks/intel
@load ./where-locations
event file_hash(f: fa_file, kind: string, hash: string)
{
local seen = Intel::Seen($indicator=hash,
$indicator_type=Intel::FILE_HASH,
$f=f,
$where=Files::IN_HASH);
Intel::seen(seen);
}

View file

@ -0,0 +1,11 @@
@load base/frameworks/intel
@load ./where-locations
event file_new(f: fa_file)
{
if ( f?$info && f$info?$filename )
Intel::seen([$indicator=f$info$filename,
$indicator_type=Intel::FILE_NAME,
$f=f,
$where=Files::IN_NAME]);
}

View file

@ -0,0 +1,53 @@
@load base/frameworks/intel
@load ./where-locations
@load base/utils/addrs
event http_header(c: connection, is_orig: bool, name: string, value: string)
{
if ( is_orig )
{
switch ( name )
{
case "HOST":
if ( is_valid_ip(value) )
Intel::seen([$host=to_addr(value),
$indicator_type=Intel::ADDR,
$conn=c,
$where=HTTP::IN_HOST_HEADER]);
else
Intel::seen([$indicator=value,
$indicator_type=Intel::DOMAIN,
$conn=c,
$where=HTTP::IN_HOST_HEADER]);
break;
case "REFERER":
Intel::seen([$indicator=sub(value, /^.*:\/\//, ""),
$indicator_type=Intel::URL,
$conn=c,
$where=HTTP::IN_REFERRER_HEADER]);
break;
case "X-FORWARDED-FOR":
if ( is_valid_ip(value) )
{
local addrs = find_ip_addresses(value);
for ( i in addrs )
{
Intel::seen([$host=to_addr(addrs[i]),
$indicator_type=Intel::ADDR,
$conn=c,
$where=HTTP::IN_X_FORWARDED_FOR_HEADER]);
}
}
break;
case "USER-AGENT":
Intel::seen([$indicator=value,
$indicator_type=Intel::SOFTWARE,
$conn=c,
$where=HTTP::IN_USER_AGENT_HEADER]);
break;
}
}
}

View file

@ -1,11 +0,0 @@
@load base/frameworks/intel
@load ./where-locations
event http_header(c: connection, is_orig: bool, name: string, value: string)
{
if ( is_orig && name == "HOST" )
Intel::seen([$indicator=value,
$indicator_type=Intel::DOMAIN,
$conn=c,
$where=HTTP::IN_HOST_HEADER]);
}

View file

@ -1,12 +0,0 @@
@load base/frameworks/intel
@load ./where-locations
event http_header(c: connection, is_orig: bool, name: string, value: string)
{
if ( is_orig && name == "USER-AGENT" )
Intel::seen([$indicator=value,
$indicator_type=Intel::SOFTWARE,
$conn=c,
$where=HTTP::IN_USER_AGENT_HEADER]);
}

View file

@ -2,31 +2,9 @@
@load base/protocols/ssl
@load ./where-locations
event x509_certificate(c: connection, is_orig: bool, cert: X509, chain_idx: count, chain_len: count, der_cert: string)
event ssl_extension_server_name(c: connection, is_orig: bool, names: string_vec)
{
if ( chain_idx == 0 )
{
if ( /emailAddress=/ in cert$subject )
{
local email = sub(cert$subject, /^.*emailAddress=/, "");
email = sub(email, /,.*$/, "");
Intel::seen([$indicator=email,
$indicator_type=Intel::EMAIL,
$conn=c,
$where=(is_orig ? SSL::IN_CLIENT_CERT : SSL::IN_SERVER_CERT)]);
}
Intel::seen([$indicator=sha1_hash(der_cert),
$indicator_type=Intel::CERT_HASH,
$conn=c,
$where=(is_orig ? SSL::IN_CLIENT_CERT : SSL::IN_SERVER_CERT)]);
}
}
event ssl_extension(c: connection, is_orig: bool, code: count, val: string)
{
if ( is_orig && SSL::extensions[code] == "server_name" &&
c?$ssl && c$ssl?$server_name )
if ( is_orig && c?$ssl && c$ssl?$server_name )
Intel::seen([$indicator=c$ssl$server_name,
$indicator_type=Intel::DOMAIN,
$conn=c,

View file

@ -4,10 +4,14 @@ export {
redef enum Intel::Where += {
Conn::IN_ORIG,
Conn::IN_RESP,
Files::IN_HASH,
Files::IN_NAME,
DNS::IN_REQUEST,
DNS::IN_RESPONSE,
HTTP::IN_HOST_HEADER,
HTTP::IN_REFERRER_HEADER,
HTTP::IN_USER_AGENT_HEADER,
HTTP::IN_X_FORWARDED_FOR_HEADER,
HTTP::IN_URL,
SMTP::IN_MAIL_FROM,
SMTP::IN_RCPT_TO,
@ -17,9 +21,8 @@ export {
SMTP::IN_REPLY_TO,
SMTP::IN_X_ORIGINATING_IP_HEADER,
SMTP::IN_MESSAGE,
SSL::IN_SERVER_CERT,
SSL::IN_CLIENT_CERT,
SSL::IN_SERVER_NAME,
SMTP::IN_HEADER,
X509::IN_CERT,
};
}

View file

@ -0,0 +1,16 @@
@load base/frameworks/intel
@load base/files/x509
@load ./where-locations
event x509_certificate(f: fa_file, cert_ref: opaque of x509, cert: X509::Certificate)
{
if ( /emailAddress=/ in cert$subject )
{
local email = sub(cert$subject, /^.*emailAddress=/, "");
email = sub(email, /,.*$/, "");
Intel::seen([$indicator=email,
$indicator_type=Intel::EMAIL,
$f=f,
$where=X509::IN_CERT]);
}
}

View file

@ -8,23 +8,23 @@ export {
const max_bpf_shunts = 100 &redef;
## Call this function to use BPF to shunt a connection (to prevent the
## data packets from reaching Bro). For TCP connections, control packets
## are still allowed through so that Bro can continue logging the connection
## and it can stop shunting once the connection ends.
## data packets from reaching Bro). For TCP connections, control
## packets are still allowed through so that Bro can continue logging
## the connection and it can stop shunting once the connection ends.
global shunt_conn: function(id: conn_id): bool;
## This function will use a BPF expresssion to shunt traffic between
## This function will use a BPF expression to shunt traffic between
## the two hosts given in the `conn_id` so that the traffic is never
## exposed to Bro's traffic processing.
global shunt_host_pair: function(id: conn_id): bool;
## Remove shunting for a host pair given as a `conn_id`. The filter
## is not immediately removed. It waits for the occassional filter
## is not immediately removed. It waits for the occasional filter
## update done by the `PacketFilter` framework.
global unshunt_host_pair: function(id: conn_id): bool;
## Performs the same function as the `unshunt_host_pair` function, but
## it forces an immediate filter update.
## Performs the same function as the :bro:id:`PacketFilter::unshunt_host_pair`
## function, but it forces an immediate filter update.
global force_unshunt_host_pair: function(id: conn_id): bool;
## Retrieve the currently shunted connections.
@ -34,12 +34,13 @@ export {
global current_shunted_host_pairs: function(): set[conn_id];
redef enum Notice::Type += {
## Indicative that :bro:id:`PacketFilter::max_bpf_shunts` connections
## are already being shunted with BPF filters and no more are allowed.
## Indicative that :bro:id:`PacketFilter::max_bpf_shunts`
## connections are already being shunted with BPF filters and
## no more are allowed.
No_More_Conn_Shunts_Available,
## Limitations in BPF make shunting some connections with BPF impossible.
## This notice encompasses those various cases.
## Limitations in BPF make shunting some connections with BPF
## impossible. This notice encompasses those various cases.
Cannot_BPF_Shunt_Conn,
};
}

View file

@ -1,4 +1,4 @@
##! Provides the possibly to define software names that are interesting to
##! Provides the possibility to define software names that are interesting to
##! watch for changes. A notice is generated if software versions change on a
##! host.
@ -9,15 +9,15 @@ module Software;
export {
redef enum Notice::Type += {
## For certain software, 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
## For certain software, 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
## :bro:id:`Software::interesting_version_changes` variable.
Software_Version_Change,
};
## Some software is more interesting when the version changes and this is
## a set of all software that should raise a notice when a different
## Some software is more interesting when the version changes and this
## is a set of all software that should raise a notice when a different
## version is seen on a host.
const interesting_version_changes: set[string] = { } &redef;
}

View file

@ -1,5 +1,5 @@
##! Provides a variable to define vulnerable versions of software and if a
##! a version of that software as old or older than the defined version a
##! Provides a variable to define vulnerable versions of software and if
##! a version of that software is as old or older than the defined version a
##! notice will be generated.
@load base/frameworks/control
@ -21,7 +21,7 @@ export {
min: Software::Version &optional;
## The maximum vulnerable version. This field is deliberately
## not optional because a maximum vulnerable version must
## always be defined. This assumption may become incorrent
## always be defined. This assumption may become incorrect
## if all future versions of some software are to be considered
## vulnerable. :)
max: Software::Version;

View file

@ -0,0 +1,69 @@
##! Windows systems access a Microsoft Certificate Revocation List (CRL) periodically. The
##! user agent for these requests reveals which version of Crypt32.dll installed on the system,
##! which can uniquely identify the version of Windows that's running.
##!
##! This script will log the version of Windows that was identified to the Software framework.
@load base/protocols/http
@load base/frameworks/software
module OS;
export {
redef enum Software::Type += {
## Identifier for Windows operating system versions
WINDOWS,
};
type Software::name_and_version: record {
name : string;
version: Software::Version;
};
const crypto_api_mapping: table[string] of Software::name_and_version = {
["Microsoft-CryptoAPI/5.131.2195.6661"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2195, $minor3=6661, $addl="2000 SP4"]],
["Microsoft-CryptoAPI/5.131.2195.6824"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2195, $minor3=6824, $addl="2000 with MS04-11"]],
["Microsoft-CryptoAPI/5.131.2195.6926"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2195, $minor3=6926, $addl="2000 with Hotfix 98830"]],
["Microsoft-CryptoAPI/5.131.2600.0"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=0, $addl="XP SP0"]],
["Microsoft-CryptoAPI/5.131.2600.1106"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=1106, $addl="XP SP1"]],
["Microsoft-CryptoAPI/5.131.2600.2180"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=2180, $addl="XP SP2"]],
["Microsoft-CryptoAPI/5.131.2600.3180"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=3180, $addl="XP SP3 Beta 1"]],
["Microsoft-CryptoAPI/5.131.2600.3205"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=3205, $addl="XP SP3 Beta 2"]],
["Microsoft-CryptoAPI/5.131.2600.3249"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=3249, $addl="XP SP3 RC Beta"]],
["Microsoft-CryptoAPI/5.131.2600.3264"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=3264, $addl="XP SP3 RC1"]],
["Microsoft-CryptoAPI/5.131.2600.3282"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=3282, $addl="XP SP3 RC1 Update"]],
["Microsoft-CryptoAPI/5.131.2600.3300"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=3300, $addl="XP SP3 RC2"]],
["Microsoft-CryptoAPI/5.131.2600.3311"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=3311, $addl="XP SP3 RC2 Update"]],
["Microsoft-CryptoAPI/5.131.2600.5508"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=5508, $addl="XP SP3 RC2 Update 2"]],
["Microsoft-CryptoAPI/5.131.2600.5512"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=2600, $minor3=5512, $addl="XP SP3"]],
["Microsoft-CryptoAPI/5.131.3790.0"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=3790, $minor3=0, $addl="XP x64 or Server 2003 SP0"]],
["Microsoft-CryptoAPI/5.131.3790.1830"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=3790, $minor3=1830, $addl="XP x64 or Server 2003 SP1"]],
["Microsoft-CryptoAPI/5.131.3790.3959"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=3790, $minor3=3959, $addl="XP x64 or Server 2003 SP2"]],
["Microsoft-CryptoAPI/5.131.3790.5235"] = [$name="Windows", $version=[$major=5, $minor=131, $minor2=3790, $minor3=5235, $addl="XP x64 or Server 2003 with MS13-095"]],
["Microsoft-CryptoAPI/6.0"] = [$name="Windows", $version=[$major=6, $minor=0, $addl="Vista or Server 2008"]],
["Microsoft-CryptoAPI/6.1"] = [$name="Windows", $version=[$major=6, $minor=1, $addl="7 or Server 2008 R2"]],
["Microsoft-CryptoAPI/6.2"] = [$name="Windows", $version=[$major=6, $minor=2, $addl="8 or Server 2012"]],
["Microsoft-CryptoAPI/6.3"] = [$name="Windows", $version=[$major=6, $minor=3, $addl="8.1 or Server 2012 R2"]],
["Microsoft-CryptoAPI/6.4"] = [$name="Windows", $version=[$major=6, $minor=4, $addl="10 Technical Preview"]],
} &redef;
}
event HTTP::log_http(rec: HTTP::Info) &priority=5
{
if ( rec?$host && rec?$user_agent && rec$host == "crl.microsoft.com" &&
/Microsoft-CryptoAPI\// in rec$user_agent )
{
if ( rec$user_agent !in crypto_api_mapping )
{
Software::found(rec$id, [$unparsed_version=sub(rec$user_agent, /Microsoft-CryptoAPI/, "Unknown CryptoAPI Version"), $host=rec$id$orig_h, $software_type=WINDOWS]);
}
else
{
local result = crypto_api_mapping[rec$user_agent];
Software::found(rec$id, [$version=result$version, $name=result$name, $host=rec$id$orig_h, $software_type=WINDOWS]);
}
}
}

View file

@ -0,0 +1 @@
Integration with Barnyard2.

View file

@ -15,8 +15,8 @@ export {
alert: AlertData &log;
};
## This can convert a Barnyard :bro:type:`Barnyard2::PacketID` value to a
## :bro:type:`conn_id` value in the case that you might need to index
## This can convert a Barnyard :bro:type:`Barnyard2::PacketID` value to
## a :bro:type:`conn_id` value in the case that you might need to index
## into an existing data structure elsewhere within Bro.
global pid2cid: function(p: PacketID): conn_id;
}

View file

@ -11,7 +11,7 @@ export {
generator_id: count; ##< Which generator generated the alert?
signature_revision: count; ##< Sig revision for this id.
classification_id: count; ##< Event classification.
classification: string; ##< Descriptive classification string,
classification: string; ##< Descriptive classification string.
priority_id: count; ##< Event priority.
event_id: count; ##< Event ID.
} &log;

View file

@ -3,8 +3,8 @@
module Intel;
## These are some fields to add extended compatibility between Bro and the Collective
## Intelligence Framework
## These are some fields to add extended compatibility between Bro and the
## Collective Intelligence Framework.
redef record Intel::MetaData += {
## Maps to the Impact field in the Collective Intelligence Framework.
cif_impact: string &optional;

View file

@ -0,0 +1 @@
AppStats collects information about web applications in use on the network.

View file

@ -1,5 +1,5 @@
#! AppStats collects information about web applications in use
#! on the network.
##! AppStats collects information about web applications in use
##! on the network.
@load base/protocols/http
@load base/protocols/ssl

View file

@ -0,0 +1 @@
Plugins for AppStats.

View file

@ -1,6 +1,6 @@
@load ./facebook
@load ./gmail
@load ./google
@load ./netflix
@load ./pandora
@load ./youtube
#@load ./gmail
#@load ./google
#@load ./netflix
#@load ./pandora
#@load ./youtube

View file

@ -4,7 +4,7 @@
##! the packet capture or it could even be beyond the host. If you are
##! capturing from a switch with a SPAN port, it's very possible that
##! the switch itself could be overloaded and dropping packets.
##! Reported loss is computed in terms of number of "gap events" (ACKs
##! Reported loss is computed in terms of the number of "gap events" (ACKs
##! for a sequence number that's above a gap).
@load base/frameworks/notice
@ -26,7 +26,7 @@ export {
## The time delay between this measurement and the last.
ts_delta: interval &log;
## In the event that there are multiple Bro instances logging
## to the same host, this distinguishes each peer with it's
## to the same host, this distinguishes each peer with its
## individual name.
peer: string &log;
## Number of missed ACKs from the previous measurement interval.
@ -34,7 +34,7 @@ export {
## Total number of ACKs seen in the previous measurement interval.
acks: count &log;
## Percentage of ACKs seen where the data being ACKed wasn't seen.
percent_lost: string &log;
percent_lost: double &log;
};
## The interval at which capture loss reports are created.
@ -43,7 +43,7 @@ export {
## The percentage of missed data that is considered "too much"
## when the :bro:enum:`CaptureLoss::Too_Much_Loss` notice should be
## generated. The value is expressed as a double between 0 and 1 with 1
## being 100%
## being 100%.
const too_much_loss: double = 0.1 &redef;
}
@ -64,7 +64,7 @@ event CaptureLoss::take_measurement(last_ts: time, last_acks: count, last_gaps:
$ts_delta=now-last_ts,
$peer=peer_description,
$acks=acks, $gaps=gaps,
$percent_lost=fmt("%.3f%%", pct_lost)];
$percent_lost=pct_lost];
if ( pct_lost >= too_much_loss*100 )
NOTICE([$note=Too_Much_Loss,

View file

@ -0,0 +1 @@
Detect hosts that are running traceroute.

View file

@ -1,7 +1,8 @@
##! This script detects a large number of ICMP Time Exceeded messages heading toward
##! hosts that have sent low TTL packets. It generates a notice when the number of
##! ICMP Time Exceeded messages for a source-destination pair exceeds a
##! threshold.
##! This script detects a large number of ICMP Time Exceeded messages heading
##! toward hosts that have sent low TTL packets. It generates a notice when the
##! number of ICMP Time Exceeded messages for a source-destination pair exceeds
##! a threshold.
@load base/frameworks/sumstats
@load base/frameworks/signatures
@load-sigs ./detect-low-ttls.sig
@ -20,15 +21,16 @@ export {
Detected
};
## By default this script requires that any host detected running traceroutes
## first send low TTL packets (TTL < 10) to the traceroute destination host.
## Changing this this setting to `F` will relax the detection a bit by
## solely relying on ICMP time-exceeded messages to detect traceroute.
## By default this script requires that any host detected running
## traceroutes first send low TTL packets (TTL < 10) to the traceroute
## destination host. Changing this setting to F will relax the
## detection a bit by solely relying on ICMP time-exceeded messages to
## detect traceroute.
const require_low_ttl_packets = T &redef;
## Defines the threshold for ICMP Time Exceeded messages for a src-dst pair.
## This threshold only comes into play after a host is found to be
## sending low ttl packets.
## Defines the threshold for ICMP Time Exceeded messages for a src-dst
## pair. This threshold only comes into play after a host is found to
## be sending low TTL packets.
const icmp_time_exceeded_threshold: double = 3 &redef;
## Interval at which to watch for the
@ -40,7 +42,7 @@ export {
type Info: record {
## Timestamp
ts: time &log;
## Address initiaing the traceroute.
## Address initiating the traceroute.
src: addr &log;
## Destination address of the traceroute.
dst: addr &log;

View file

@ -0,0 +1,40 @@
##! This script dumps the events that Bro raises out to standard output in a
##! readable form. This is for debugging only and allows to understand events and
##! their parameters as Bro processes input. Note that it will show only events
##! for which a handler is defined.
module DumpEvents;
export {
## If true, include event arguments in output.
const include_args = T &redef;
## Only include events matching the given pattern into output. By default, the
## pattern matches all events.
const include = /.*/ &redef;
}
event new_event(name: string, args: call_argument_vector)
{
if ( include !in name )
return;
print fmt("%17.6f %s", network_time(), name);
if ( ! include_args || |args| == 0 )
return;
for ( i in args )
{
local a = args[i];
local proto = fmt("%s: %s", a$name, a$type_name);
if ( a?$value )
print fmt(" [%d] %-18s = %s", i, proto, a$value);
else
print fmt(" | %-18s = %s [default]", proto, a$value);
}
print "";
}

View file

@ -1,13 +1,13 @@
##! This script provides infrastructure for logging devices for which Bro has been
##! able to determine the MAC address, and it logs them once per day (by default).
##! The log that is output provides an easy way to determine a count of the devices
##! in use on a network per day.
##! This script provides infrastructure for logging devices for which Bro has
##! been able to determine the MAC address, and it logs them once per day (by
##! default). The log that is output provides an easy way to determine a count
##! of the devices in use on a network per day.
##!
##! ..note::
##! .. note::
##!
##! This script will not generate any logs on its own, it needs to be
##! supplied with information from elsewhere, such as
##! :doc:`policy/protocols/dhcp/known-devices-and-hostnames/scripts/.
##! :doc:`/scripts/policy/protocols/dhcp/known-devices-and-hostnames.bro`.
module Known;
@ -15,7 +15,8 @@ export {
## The known-hosts logging stream identifier.
redef enum Log::ID += { DEVICES_LOG };
## The record type which contains the column fields of the known-devices log.
## The record type which contains the column fields of the known-devices
## log.
type DevicesInfo: record {
## The timestamp at which the host was detected.
ts: time &log;
@ -24,10 +25,10 @@ export {
};
## The set of all known MAC addresses. It can accessed from other
## to add, and check for, addresses seen in use.
##
## We maintain each entry for 24 hours by default so that the existence of
## individual addressed is logged each day.
## scripts to add, and check for, addresses seen in use.
##
## We maintain each entry for 24 hours by default so that the existence
## of individual addresses is logged each day.
global known_devices: set[string] &create_expire=1day &synchronized &redef;
## An event that can be handled to access the :bro:type:`Known::DevicesInfo`

View file

@ -29,9 +29,10 @@ export {
#global confirm_filter_installation: event(success: bool);
redef record Cluster::Node += {
## A BPF filter for load balancing traffic sniffed on a single interface
## across a number of processes. In normal uses, this will be assigned
## dynamically by the manager and installed by the workers.
## A BPF filter for load balancing traffic sniffed on a single
## interface across a number of processes. In normal uses, this
## will be assigned dynamically by the manager and installed by
## the workers.
lb_filter: string &optional;
};
}
@ -81,7 +82,7 @@ event bro_init() &priority=5
++lb_proc_track[that_node$ip, that_node$interface];
if ( total_lb_procs > 1 )
{
that_node$lb_filter = PacketFilter::sample_filter(total_lb_procs, this_lb_proc);
that_node$lb_filter = PacketFilter::sampling_filter(total_lb_procs, this_lb_proc);
Communication::nodes[no]$capture_filter = that_node$lb_filter;
}
}

View file

@ -7,9 +7,9 @@ export {
redef enum Log::ID += { LOG };
type Info: record {
## Name of the script loaded potentially with spaces included before
## the file name to indicate load depth. The convention is two spaces
## per level of depth.
## Name of the script loaded potentially with spaces included
## before the file name to indicate load depth. The convention
## is two spaces per level of depth.
name: string &log;
};
}
@ -36,4 +36,4 @@ event bro_init() &priority=5
event bro_script_loaded(path: string, level: count)
{
Log::write(LoadedScripts::LOG, [$name=cat(depth[level], compress_path(path))]);
}
}

View file

@ -8,7 +8,8 @@ redef profiling_file = open_log_file("prof");
## Set the cheap profiling interval.
redef profiling_interval = 15 secs;
## Set the expensive profiling interval.
## Set the expensive profiling interval (multiple of
## :bro:id:`profiling_interval`).
redef expensive_profiling_multiple = 20;
event bro_init()

View file

@ -1,8 +1,8 @@
##! TCP Scan detection
##!
##! ..Authors: Sheharbano Khattak
##! Seth Hall
##! All the authors of the old scan.bro
##! TCP Scan detection.
# ..Authors: Sheharbano Khattak
# Seth Hall
# All the authors of the old scan.bro
@load base/frameworks/notice
@load base/frameworks/sumstats
@ -13,37 +13,38 @@ module Scan;
export {
redef enum Notice::Type += {
## Address scans detect that a host appears to be scanning some number
## of destinations on a single port. This notice is generated when more
## than :bro:id:`Scan::addr_scan_threshold` unique hosts are seen over
## the previous :bro:id:`Scan::addr_scan_interval` time range.
## Address scans detect that a host appears to be scanning some
## number of destinations on a single port. This notice is
## generated when more than :bro:id:`Scan::addr_scan_threshold`
## unique hosts are seen over the previous
## :bro:id:`Scan::addr_scan_interval` time range.
Address_Scan,
## Port scans detect that an attacking host appears to be scanning a
## single victim host on several ports. This notice is generated when
## an attacking host attempts to connect to
## Port scans detect that an attacking host appears to be
## scanning a single victim host on several ports. This notice
## is generated when an attacking host attempts to connect to
## :bro:id:`Scan::port_scan_threshold`
## unique ports on a single host over the previous
## :bro:id:`Scan::port_scan_interval` time range.
Port_Scan,
};
## Failed connection attempts are tracked over this time interval for the address
## scan detection. A higher interval will detect slower scanners, but may also
## yield more false positives.
## Failed connection attempts are tracked over this time interval for
## the address scan detection. A higher interval will detect slower
## scanners, but may also yield more false positives.
const addr_scan_interval = 5min &redef;
## Failed connection attempts are tracked over this time interval for the port scan
## detection. A higher interval will detect slower scanners, but may also yield
## more false positives.
## Failed connection attempts are tracked over this time interval for
## the port scan detection. A higher interval will detect slower
## scanners, but may also yield more false positives.
const port_scan_interval = 5min &redef;
## The threshold of a unique number of hosts a scanning host has to have failed
## connections with on a single port.
## The threshold of the unique number of hosts a scanning host has to
## have failed connections with on a single port.
const addr_scan_threshold = 25.0 &redef;
## The threshold of a number of unique ports a scanning host has to have failed
## connections with on a single victim host.
## The threshold of the number of unique ports a scanning host has to
## have failed connections with on a single victim host.
const port_scan_threshold = 15.0 &redef;
global Scan::addr_scan_policy: hook(scanner: addr, victim: addr, scanned_port: port);
@ -52,7 +53,7 @@ export {
event bro_init() &priority=5
{
local r1: SumStats::Reducer = [$stream="scan.addr.fail", $apply=set(SumStats::UNIQUE)];
local r1: SumStats::Reducer = [$stream="scan.addr.fail", $apply=set(SumStats::UNIQUE), $unique_max=double_to_count(addr_scan_threshold+2)];
SumStats::create([$name="addr-scan",
$epoch=addr_scan_interval,
$reducers=set(r1),
@ -77,7 +78,7 @@ event bro_init() &priority=5
}]);
# Note: port scans are tracked similar to: table[src_ip, dst_ip] of set(port);
local r2: SumStats::Reducer = [$stream="scan.port.fail", $apply=set(SumStats::UNIQUE)];
local r2: SumStats::Reducer = [$stream="scan.port.fail", $apply=set(SumStats::UNIQUE), $unique_max=double_to_count(port_scan_threshold+2)];
SumStats::create([$name="port-scan",
$epoch=port_scan_interval,
$reducers=set(r2),
@ -146,11 +147,6 @@ function is_reverse_failed_conn(c: connection): bool
return F;
}
## Generated for an unsuccessful connection attempt. This
## event is raised when an originator unsuccessfully attempted
## to establish a connection. “Unsuccessful” is defined as at least
## tcp_attempt_delay seconds having elapsed since the originator first sent a
## connection establishment packet to the destination without seeing a reply.
event connection_attempt(c: connection)
{
local is_reverse_scan = F;
@ -160,9 +156,6 @@ event connection_attempt(c: connection)
add_sumstats(c$id, is_reverse_scan);
}
## Generated for a rejected TCP connection. This event is raised when an originator
## attempted to setup a TCP connection but the responder replied with a RST packet
## denying it.
event connection_rejected(c: connection)
{
local is_reverse_scan = F;
@ -172,8 +165,6 @@ event connection_rejected(c: connection)
add_sumstats(c$id, is_reverse_scan);
}
## Generated when an endpoint aborted a TCP connection. The event is raised when
## one endpoint of an *established* TCP connection aborted by sending a RST packet.
event connection_reset(c: connection)
{
if ( is_failed_conn(c) )
@ -182,7 +173,6 @@ event connection_reset(c: connection)
add_sumstats(c$id, T);
}
## Generated for each still-open connection when Bro terminates.
event connection_pending(c: connection)
{
if ( is_failed_conn(c) )

View file

@ -1,4 +1,5 @@
##! Log memory/packet/lag statistics. Differs from profiling.bro in that this
##! Log memory/packet/lag statistics. Differs from
##! :doc:`/scripts/policy/misc/profiling.bro` in that this
##! is lighter-weight (much less info, and less load to generate).
@load base/frameworks/notice
@ -20,21 +21,23 @@ export {
mem: count &log;
## Number of packets processed since the last stats interval.
pkts_proc: count &log;
## Number of events that been processed since the last stats interval.
## Number of events processed since the last stats interval.
events_proc: count &log;
## Number of events that have been queued since the last stats interval.
## Number of events that have been queued since the last stats
## interval.
events_queued: count &log;
## Lag between the wall clock and packet timestamps if reading live traffic.
## Lag between the wall clock and packet timestamps if reading
## live traffic.
lag: interval &log &optional;
## Number of packets received since the last stats interval if reading
## live traffic.
## Number of packets received since the last stats interval if
## reading live traffic.
pkts_recv: count &log &optional;
## Number of packets dropped since the last stats interval if reading
## live traffic.
## Number of packets dropped since the last stats interval if
## reading live traffic.
pkts_dropped: count &log &optional;
## Number of packets seen on the link since the last stats interval
## if reading live traffic.
## Number of packets seen on the link since the last stats
## interval if reading live traffic.
pkts_link: count &log &optional;
};

View file

@ -1,4 +1,4 @@
##! Deletes the -w tracefile at regular intervals and starts a new file
##! Deletes the ``-w`` tracefile at regular intervals and starts a new file
##! from scratch.
module TrimTraceFile;
@ -8,9 +8,9 @@ export {
const trim_interval = 10 mins &redef;
## This event can be generated externally to this script if on-demand
## tracefile rotation is required with the caveat that the script doesn't
## currently attempt to get back on schedule automatically and the next
## trim will likely won't happen on the
## tracefile rotation is required with the caveat that the script
## doesn't currently attempt to get back on schedule automatically and
## the next trim likely won't happen on the
## :bro:id:`TrimTraceFile::trim_interval`.
global go: event(first_trim: bool);
}

View file

@ -15,8 +15,8 @@ export {
type HostsInfo: record {
## The timestamp at which the host was detected.
ts: time &log;
## The address that was detected originating or responding to a TCP
## connection.
## The address that was detected originating or responding to a
## TCP connection.
host: addr &log;
};

View file

@ -7,7 +7,7 @@ module Known;
export {
redef record DevicesInfo += {
## The value of the DHCP host name option, if seen
## The value of the DHCP host name option, if seen.
dhcp_host_name: string &log &optional;
};
}

View file

@ -19,13 +19,17 @@ export {
};
}
event DNS::do_reply(c: connection, msg: dns_msg, ans: dns_answer, reply: string) &priority=4
hook DNS::do_reply(c: connection, msg: dns_msg, ans: dns_answer, reply: string) &priority=5
{
# The "ready" flag will be set here. This causes the setting from the
# base script to be overridden since the base script will log immediately
# after all of the ANS replies have been seen.
c$dns$ready=F;
if ( msg$opcode != 0 )
# Currently only standard queries are tracked.
return;
if ( ! msg$QR )
# This is weird: the inquirer must also be providing answers in
# the request, which is not what we want to track.
return;
if ( ans$answer_type == DNS_AUTH )
{
if ( ! c$dns?$auth )
@ -38,11 +42,4 @@ event DNS::do_reply(c: connection, msg: dns_msg, ans: dns_answer, reply: string)
c$dns$addl = set();
add c$dns$addl[reply];
}
if ( c$dns?$answers && c$dns?$auth && c$dns?$addl &&
c$dns$total_replies == |c$dns$answers| + |c$dns$auth| + |c$dns$addl| )
{
# *Now* all replies desired have been seen.
c$dns$ready = T;
}
}

View file

@ -10,9 +10,9 @@ module DNS;
export {
redef enum Notice::Type += {
## Raised when a non-local name is found to be pointing at a local host.
## :bro:id:`Site::local_zones` variable **must** be set appropriately
## for this detection.
## Raised when a non-local name is found to be pointing at a
## local host. The :bro:id:`Site::local_zones` variable
## **must** be set appropriately for this detection.
External_Name,
};
}

View file

@ -1,5 +1,5 @@
##! FTP brute-forcing detector, triggering when too many rejected usernames or
##! failed passwords have occured from a single address.
##! FTP brute-forcing detector, triggering when too many rejected usernames or
##! failed passwords have occurred from a single address.
@load base/protocols/ftp
@load base/frameworks/sumstats
@ -10,8 +10,8 @@ module FTP;
export {
redef enum Notice::Type += {
## Indicates a host bruteforcing FTP logins by watching for too many
## rejected usernames or failed passwords.
## Indicates a host bruteforcing FTP logins by watching for too
## many rejected usernames or failed passwords.
Bruteforcing
};
@ -27,7 +27,7 @@ export {
event bro_init()
{
local r1: SumStats::Reducer = [$stream="ftp.failed_auth", $apply=set(SumStats::UNIQUE)];
local r1: SumStats::Reducer = [$stream="ftp.failed_auth", $apply=set(SumStats::UNIQUE), $unique_max=double_to_count(bruteforce_threshold+2)];
SumStats::create([$name="ftp-detect-bruteforcing",
$epoch=bruteforce_measurement_interval,
$reducers=set(r1),

View file

@ -8,10 +8,12 @@ module HTTP;
export {
redef enum Notice::Type += {
## Indicates that a host performing SQL injection attacks was detected.
## Indicates that a host performing SQL injection attacks was
## detected.
SQL_Injection_Attacker,
## Indicates that a host was seen to have SQL injection attacks against
## it. This is tracked by IP address as opposed to hostname.
## Indicates that a host was seen to have SQL injection attacks
## against it. This is tracked by IP address as opposed to
## hostname.
SQL_Injection_Victim,
};
@ -19,9 +21,11 @@ export {
## Indicator of a URI based SQL injection attack.
URI_SQLI,
## Indicator of client body based SQL injection attack. This is
## typically the body content of a POST request. Not implemented yet.
## typically the body content of a POST request. Not implemented
## yet.
POST_SQLI,
## Indicator of a cookie based SQL injection attack. Not implemented yet.
## Indicator of a cookie based SQL injection attack. Not
## implemented yet.
COOKIE_SQLI,
};

View file

@ -8,12 +8,12 @@ module HTTP;
export {
redef record Info += {
## The vector of HTTP header names sent by the client. No header
## values are included here, just the header names.
## The vector of HTTP header names sent by the client. No
## header values are included here, just the header names.
client_header_names: vector of string &log &optional;
## The vector of HTTP header names sent by the server. No header
## values are included here, just the header names.
## The vector of HTTP header names sent by the server. No
## header values are included here, just the header names.
server_header_names: vector of string &log &optional;
};

View file

@ -1,4 +1,4 @@
##! Extracts and logs variables names from cookies sent by clients.
##! Extracts and logs variable names from cookies sent by clients.
@load base/protocols/http/main
@load base/protocols/http/utils

View file

@ -1,4 +1,4 @@
##! Extracts and log variables from the requested URI in the default HTTP
##! Extracts and logs variables from the requested URI in the default HTTP
##! logging stream.
@load base/protocols/http

View file

@ -1,8 +1,8 @@
##! Script for tracking known Modbus masters and slaves.
##!
##! .. todo: This script needs a lot of work. What might be more interesting is to track
##! master/slave relationships based on commands sent and successful (non-exception)
##! responses.
##! .. todo:: This script needs a lot of work. What might be more interesting
##! is to track master/slave relationships based on commands sent and
##! successful (non-exception) responses.
@load base/protocols/modbus

View file

@ -1,7 +1,7 @@
##! This script tracks the memory map of holding (read/write) registers and logs
##! changes as they are discovered.
##!
##! .. todo: Not all register reads and write functions are being supported yet.
##! .. todo:: Not all register read and write functions are supported yet.
@load base/protocols/modbus
@load base/utils/directions-and-hosts
@ -15,9 +15,9 @@ export {
const track_memmap: Host = ALL_HOSTS &redef;
type MemmapInfo: record {
## Timestamp for the detected register change
## Timestamp for the detected register change.
ts: time &log;
## Unique ID for the connection
## Unique ID for the connection.
uid: string &log;
## Connection ID.
id: conn_id &log;
@ -27,7 +27,8 @@ export {
old_val: count &log;
## The new value stored in the register.
new_val: count &log;
## The time delta between when the 'old_val' and 'new_val' were seen.
## The time delta between when the *old_val* and *new_val* were
## seen.
delta: interval &log;
};
@ -42,8 +43,8 @@ export {
## The memory map of slaves is tracked with this variable.
global device_registers: table[addr] of Registers;
## This event is generated every time a register is seen to be different than
## it was previously seen to be.
## This event is generated every time a register is seen to be different
## than it was previously seen to be.
global changed_register: event(c: connection, register: count, old_val: count, new_val: count, delta: interval);
}

View file

@ -0,0 +1,20 @@
##! Software identification and extraction for MySQL traffic.
@load base/frameworks/software
module MySQL;
export {
redef enum Software::Type += {
## Identifier for MySQL servers in the software framework.
SERVER,
};
}
event mysql_server_version(c: connection, ver: string)
{
if ( ver == "" )
return;
Software::found(c$id, [$unparsed_version=ver, $host=c$id$resp_h, $software_type=SERVER]);
}

View file

@ -8,8 +8,8 @@ export {
Suspicious_Origination
};
## Places where it's suspicious for mail to originate from represented as
## all-capital, two character country codes (e.x. US). It requires
## Places where it's suspicious for mail to originate from represented
## as all-capital, two character country codes (e.g., US). It requires
## libGeoIP support built in.
const suspicious_origination_countries: set[string] = {} &redef;
const suspicious_origination_networks: set[subnet] = {} &redef;

View file

@ -5,7 +5,7 @@
##! TODO:
##!
##! * Find some heuristic to determine if email was sent through
##! a MS Exhange webmail interface as opposed to a desktop client.
##! a MS Exchange webmail interface as opposed to a desktop client.
@load base/frameworks/software/main
@load base/protocols/smtp/main
@ -20,19 +20,19 @@ export {
};
redef record Info += {
## Boolean indicator of if the message was sent through a webmail
## interface.
## Boolean indicator of if the message was sent through a
## webmail interface.
is_webmail: bool &log &default=F;
};
## Assuming that local mail servers are more trustworthy with the headers
## they insert into messages envelopes, this default makes Bro not attempt
## to detect software in inbound message bodies. If mail coming in from
## external addresses gives incorrect data in the Received headers, it
## could populate your SOFTWARE logging stream with incorrect data.
## If you would like to detect mail clients for incoming messages
## (network traffic originating from a non-local address), set this
## variable to EXTERNAL_HOSTS or ALL_HOSTS.
## Assuming that local mail servers are more trustworthy with the
## headers they insert into message envelopes, this default makes Bro
## not attempt to detect software in inbound message bodies. If mail
## coming in from external addresses gives incorrect data in
## the Received headers, it could populate your SOFTWARE logging stream
## with incorrect data. If you would like to detect mail clients for
## incoming messages (network traffic originating from a non-local
## address), set this variable to EXTERNAL_HOSTS or ALL_HOSTS.
const detect_clients_in_messages_from = LOCAL_HOSTS &redef;
## A regular expression to match USER-AGENT-like headers to find if a

View file

@ -11,12 +11,12 @@ module SSH;
export {
redef enum Notice::Type += {
## Indicates that a host has been identified as crossing the
## :bro:id:`SSH::password_guesses_limit` threshold with heuristically
## determined failed logins.
## :bro:id:`SSH::password_guesses_limit` threshold with
## heuristically determined failed logins.
Password_Guessing,
## Indicates that a host previously identified as a "password guesser"
## has now had a heuristically successful login attempt. This is not
## currently implemented.
## Indicates that a host previously identified as a "password
## guesser" has now had a heuristically successful login
## attempt. This is not currently implemented.
Login_By_Password_Guesser,
};
@ -29,8 +29,8 @@ export {
## guessing passwords.
const password_guesses_limit: double = 30 &redef;
## The amount of time to remember presumed non-successful logins to build
## model of a password guesser.
## The amount of time to remember presumed non-successful logins to
## build a model of a password guesser.
const guessing_timeout = 30 mins &redef;
## This value can be used to exclude hosts or entire networks from being

View file

@ -7,14 +7,15 @@ module SSH;
export {
redef enum Notice::Type += {
## If an SSH login is seen to or from a "watched" country based on the
## :bro:id:`SSH::watched_countries` variable then this notice will
## be generated.
## If an SSH login is seen to or from a "watched" country based
## on the :bro:id:`SSH::watched_countries` variable then this
## notice will be generated.
Watched_Country_Login,
};
redef record Info += {
## Add geographic data related to the "remote" host of the connection.
## Add geographic data related to the "remote" host of the
## connection.
remote_location: geo_location &log &optional;
};
@ -23,21 +24,29 @@ export {
const watched_countries: set[string] = {"RO"} &redef;
}
function get_location(c: connection): geo_location
{
local lookup_ip = (c$ssh$direction == OUTBOUND) ? c$id$resp_h : c$id$orig_h;
return lookup_location(lookup_ip);
}
event SSH::heuristic_successful_login(c: connection) &priority=5
{
local location: geo_location;
location = (c$ssh$direction == OUTBOUND) ?
lookup_location(c$id$resp_h) : lookup_location(c$id$orig_h);
# Add the location data to the SSH record.
c$ssh$remote_location = location;
c$ssh$remote_location = get_location(c);
if ( location?$country_code && location$country_code in watched_countries )
if ( c$ssh$remote_location?$country_code && c$ssh$remote_location$country_code in watched_countries )
{
NOTICE([$note=Watched_Country_Login,
$conn=c,
$msg=fmt("SSH login %s watched country: %s",
(c$ssh$direction == OUTBOUND) ? "to" : "from",
location$country_code)]);
c$ssh$remote_location$country_code)]);
}
}
event SSH::heuristic_failed_login(c: connection) &priority=5
{
# Add the location data to the SSH record.
c$ssh$remote_location = get_location(c);
}

View file

@ -10,8 +10,8 @@ module SSH;
export {
redef enum Notice::Type += {
## Generated if a login originates or responds with a host where the
## reverse hostname lookup resolves to a name matched by the
## Generated if a login originates or responds with a host where
## the reverse hostname lookup resolves to a name matched by the
## :bro:id:`SSH::interesting_hostnames` regular expression.
Interesting_Hostname_Login,
};

View file

@ -1,22 +0,0 @@
##! Calculate MD5 sums for server DER formatted certificates.
@load base/protocols/ssl
module SSL;
export {
redef record Info += {
## MD5 sum of the raw server certificate.
cert_hash: string &log &optional;
};
}
event x509_certificate(c: connection, is_orig: bool, cert: X509, chain_idx: count, chain_len: count, der_cert: string) &priority=4
{
# We aren't tracking client certificates yet and we are also only tracking
# the primary cert. Watch that this came from an SSL analyzed session too.
if ( is_orig || chain_idx != 0 || ! c?$ssl )
return;
c$ssl$cert_hash = md5_hash(der_cert);
}

View file

@ -3,22 +3,22 @@
##! certificate.
@load base/protocols/ssl
@load base/files/x509
@load base/frameworks/notice
@load base/utils/directions-and-hosts
@load protocols/ssl/cert-hash
module SSL;
export {
redef enum Notice::Type += {
## Indicates that a certificate's NotValidAfter date has lapsed and
## the certificate is now invalid.
## Indicates that a certificate's NotValidAfter date has lapsed
## and the certificate is now invalid.
Certificate_Expired,
## Indicates that a certificate is going to expire within
## :bro:id:`SSL::notify_when_cert_expiring_in`.
Certificate_Expires_Soon,
## Indicates that a certificate's NotValidBefore date is future dated.
## Indicates that a certificate's NotValidBefore date is future
## dated.
Certificate_Not_Valid_Yet,
};
@ -29,35 +29,41 @@ export {
## Choices are: LOCAL_HOSTS, REMOTE_HOSTS, ALL_HOSTS, NO_HOSTS
const notify_certs_expiration = LOCAL_HOSTS &redef;
## The time before a certificate is going to expire that you would like to
## start receiving :bro:enum:`SSL::Certificate_Expires_Soon` notices.
## The time before a certificate is going to expire that you would like
## to start receiving :bro:enum:`SSL::Certificate_Expires_Soon` notices.
const notify_when_cert_expiring_in = 30days &redef;
}
event x509_certificate(c: connection, is_orig: bool, cert: X509, chain_idx: count, chain_len: count, der_cert: string) &priority=3
event ssl_established(c: connection) &priority=3
{
# If this isn't the host cert or we aren't interested in the server, just return.
if ( is_orig ||
chain_idx != 0 ||
! c$ssl?$cert_hash ||
! addr_matches_host(c$id$resp_h, notify_certs_expiration) )
# If there are no certificates or we are not interested in the server, just return.
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 ||
! addr_matches_host(c$id$resp_h, notify_certs_expiration) ||
! c$ssl$cert_chain[0]?$x509 || ! c$ssl$cert_chain[0]?$sha1 )
return;
local fuid = c$ssl$cert_chain_fuids[0];
local cert = c$ssl$cert_chain[0]$x509$certificate;
local hash = c$ssl$cert_chain[0]$sha1;
if ( cert$not_valid_before > network_time() )
NOTICE([$note=Certificate_Not_Valid_Yet,
$conn=c, $suppress_for=1day,
$msg=fmt("Certificate %s isn't valid until %T", cert$subject, cert$not_valid_before),
$identifier=cat(c$id$resp_h, c$id$resp_p, c$ssl$cert_hash)]);
$identifier=cat(c$id$resp_h, c$id$resp_p, hash),
$fuid=fuid]);
else if ( cert$not_valid_after < network_time() )
NOTICE([$note=Certificate_Expired,
$conn=c, $suppress_for=1day,
$msg=fmt("Certificate %s expired at %T", cert$subject, cert$not_valid_after),
$identifier=cat(c$id$resp_h, c$id$resp_p, c$ssl$cert_hash)]);
$identifier=cat(c$id$resp_h, c$id$resp_p, hash),
$fuid=fuid]);
else if ( cert$not_valid_after - notify_when_cert_expiring_in < network_time() )
NOTICE([$note=Certificate_Expires_Soon,
$msg=fmt("Certificate %s is going to expire at %T", cert$subject, cert$not_valid_after),
$conn=c, $suppress_for=1day,
$identifier=cat(c$id$resp_h, c$id$resp_p, c$ssl$cert_hash)]);
$identifier=cat(c$id$resp_h, c$id$resp_p, hash),
$fuid=fuid]);
}

View file

@ -2,62 +2,53 @@
##! after being converted to PEM files. The certificates will be stored in
##! a single file, one for local certificates and one for remote certificates.
##!
##! ..note::
##! .. note::
##!
##! - It doesn't work well on a cluster because each worker will write its
##! own certificate files and no duplicate checking is done across
##! clusters so each node would log each certificate.
##! own certificate files and no duplicate checking is done across the
##! cluster so each node would log each certificate.
##!
@load base/protocols/ssl
@load base/files/x509
@load base/utils/directions-and-hosts
@load protocols/ssl/cert-hash
module SSL;
export {
## Control if host certificates offered by the defined hosts
## will be written to the PEM certificates file.
## Choices are: LOCAL_HOSTS, REMOTE_HOSTS, ALL_HOSTS, NO_HOSTS
## Choices are: LOCAL_HOSTS, REMOTE_HOSTS, ALL_HOSTS, NO_HOSTS.
const extract_certs_pem = LOCAL_HOSTS &redef;
}
# This is an internally maintained variable to prevent relogging of
# certificates that have already been seen. It is indexed on an md5 sum of
# certificates that have already been seen. It is indexed on an sha1 sum of
# the certificate.
global extracted_certs: set[string] = set() &read_expire=1hr &redef;
event ssl_established(c: connection) &priority=5
{
if ( ! c$ssl?$cert )
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 ||
! c$ssl$cert_chain[0]?$x509 )
return;
if ( ! addr_matches_host(c$id$resp_h, extract_certs_pem) )
return;
if ( c$ssl$cert_hash in extracted_certs )
local hash = c$ssl$cert_chain[0]$sha1;
local cert = c$ssl$cert_chain[0]$x509$handle;
if ( hash in extracted_certs )
# If we already extracted this cert, don't do it again.
return;
add extracted_certs[c$ssl$cert_hash];
add extracted_certs[hash];
local filename = Site::is_local_addr(c$id$resp_h) ? "certs-local.pem" : "certs-remote.pem";
local outfile = open_for_append(filename);
enable_raw_output(outfile);
print outfile, "-----BEGIN CERTIFICATE-----";
print outfile, x509_get_certificate_string(cert, T);
# Encode to base64 and format to fit 50 lines. Otherwise openssl won't like it later.
local lines = split_all(encode_base64(c$ssl$cert), /.{50}/);
local i = 1;
for ( line in lines )
{
if ( |lines[i]| > 0 )
{
print outfile, lines[i];
}
i+=1;
}
print outfile, "-----END CERTIFICATE-----";
print outfile, "";
close(outfile);
}

View file

@ -0,0 +1,238 @@
##! Detect the TLS heartbleed attack. See http://heartbleed.com for more.
@load base/protocols/ssl
@load base/frameworks/notice
module Heartbleed;
export {
redef enum Notice::Type += {
## Indicates that a host performed a heartbleed attack or scan.
SSL_Heartbeat_Attack,
## Indicates that a host performing a heartbleed attack was probably successful.
SSL_Heartbeat_Attack_Success,
## Indicates we saw heartbeat requests with odd length. Probably an attack or scan.
SSL_Heartbeat_Odd_Length,
## Indicates we saw many heartbeat requests without an reply. Might be an attack.
SSL_Heartbeat_Many_Requests
};
}
# Do not disable analyzers after detection - otherwhise we will not notice
# encrypted attacks.
redef SSL::disable_analyzer_after_detection=F;
redef record SSL::Info += {
last_originator_heartbeat_request_size: count &optional;
last_responder_heartbeat_request_size: count &optional;
originator_heartbeats: count &default=0;
responder_heartbeats: count &default=0;
# Unencrypted connections - was an exploit attempt detected yet.
heartbleed_detected: bool &default=F;
# Count number of appdata packages and bytes exchanged so far.
enc_appdata_packages: count &default=0;
enc_appdata_bytes: count &default=0;
};
type min_length: record {
cipher: pattern;
min_length: count;
};
global min_lengths: vector of min_length = vector();
global min_lengths_tls11: vector of min_length = vector();
event bro_init()
{
# Minimum length a heartbeat packet must have for different cipher suites.
# Note - tls 1.1f and 1.0 have different lengths :(
# This should be all cipher suites usually supported by vulnerable servers.
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_AES_256_GCM_SHA384$/, $min_length=43];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_AES_128_GCM_SHA256$/, $min_length=43];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA384$/, $min_length=96];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA256$/, $min_length=80];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA$/, $min_length=64];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_128_CBC_SHA256$/, $min_length=80];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_128_CBC_SHA$/, $min_length=64];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=48];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_SEED_CBC_SHA$/, $min_length=64];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_IDEA_CBC_SHA$/, $min_length=48];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_DES_CBC_SHA$/, $min_length=48];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_DES40_CBC_SHA$/, $min_length=48];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_128_SHA$/, $min_length=39];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_128_MD5$/, $min_length=35];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_40_MD5$/, $min_length=35];
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC2_CBC_40_MD5$/, $min_length=48];
min_lengths[|min_lengths|] = [$cipher=/_256_CBC_SHA$/, $min_length=48];
min_lengths[|min_lengths|] = [$cipher=/_128_CBC_SHA$/, $min_length=48];
min_lengths[|min_lengths|] = [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=40];
min_lengths[|min_lengths|] = [$cipher=/_SEED_CBC_SHA$/, $min_length=48];
min_lengths[|min_lengths|] = [$cipher=/_IDEA_CBC_SHA$/, $min_length=40];
min_lengths[|min_lengths|] = [$cipher=/_DES_CBC_SHA$/, $min_length=40];
min_lengths[|min_lengths|] = [$cipher=/_DES40_CBC_SHA$/, $min_length=40];
min_lengths[|min_lengths|] = [$cipher=/_RC4_128_SHA$/, $min_length=39];
min_lengths[|min_lengths|] = [$cipher=/_RC4_128_MD5$/, $min_length=35];
min_lengths[|min_lengths|] = [$cipher=/_RC4_40_MD5$/, $min_length=35];
min_lengths[|min_lengths|] = [$cipher=/_RC2_CBC_40_MD5$/, $min_length=40];
}
event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count, payload: string)
{
if ( ! c?$ssl )
return;
if ( heartbeat_type == 1 )
{
local checklength: count = (length<(3+16)) ? length : (length - 3 - 16);
if ( payload_length > checklength )
{
c$ssl$heartbleed_detected = T;
NOTICE([$note=Heartbleed::SSL_Heartbeat_Attack,
$msg=fmt("An TLS heartbleed attack was detected! Record length %d. Payload length %d", length, payload_length),
$conn=c,
$identifier=cat(c$uid, length, payload_length)
]);
}
else if ( is_orig )
{
NOTICE([$note=Heartbleed::SSL_Heartbeat_Attack,
$msg=fmt("Heartbeat request before encryption. Probable Scan without exploit attempt. Message length: %d. Payload length: %d", length, payload_length),
$conn=c,
$n=length,
$identifier=cat(c$uid, length)
]);
}
}
if ( heartbeat_type == 2 && c$ssl$heartbleed_detected )
{
NOTICE([$note=Heartbleed::SSL_Heartbeat_Attack_Success,
$msg=fmt("An TLS heartbleed attack detected before was probably exploited. Message length: %d. Payload length: %d", length, payload_length),
$conn=c,
$identifier=c$uid
]);
}
}
event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count)
{
if ( is_orig )
++c$ssl$originator_heartbeats;
else
++c$ssl$responder_heartbeats;
local duration = network_time() - c$start_time;
if ( c$ssl$enc_appdata_packages == 0 )
NOTICE([$note=SSL_Heartbeat_Attack,
$msg=fmt("Heartbeat before ciphertext. Probable attack or scan. Length: %d, is_orig: %d", length, is_orig),
$conn=c,
$n=length,
$identifier=fmt("%s%s", c$uid, "early")
]);
else if ( duration < 1min )
NOTICE([$note=SSL_Heartbeat_Attack,
$msg=fmt("Heartbeat within first minute. Possible attack or scan. Length: %d, is_orig: %d, time: %s", length, is_orig, duration),
$conn=c,
$n=length,
$identifier=fmt("%s%s", c$uid, "early")
]);
if ( c$ssl$originator_heartbeats > c$ssl$responder_heartbeats + 3 )
NOTICE([$note=SSL_Heartbeat_Many_Requests,
$msg=fmt("More than 3 heartbeat requests without replies from server. Possible attack. Client count: %d, server count: %d", c$ssl$originator_heartbeats, c$ssl$responder_heartbeats),
$conn=c,
$n=(c$ssl$originator_heartbeats-c$ssl$responder_heartbeats),
$identifier=fmt("%s%d", c$uid, c$ssl$responder_heartbeats/1000) # re-throw every 1000 heartbeats
]);
if ( c$ssl$responder_heartbeats > c$ssl$originator_heartbeats + 3 )
NOTICE([$note=SSL_Heartbeat_Many_Requests,
$msg=fmt("Server sending more heartbeat responses than requests seen. Possible attack. Client count: %d, server count: %d", c$ssl$originator_heartbeats, c$ssl$responder_heartbeats),
$conn=c,
$n=(c$ssl$originator_heartbeats-c$ssl$responder_heartbeats),
$identifier=fmt("%s%d", c$uid, c$ssl$responder_heartbeats/1000) # re-throw every 1000 heartbeats
]);
if ( is_orig && length < 19 )
NOTICE([$note=SSL_Heartbeat_Odd_Length,
$msg=fmt("Heartbeat message smaller than minimum required length. Probable attack or scan. Message length: %d. Cipher: %s. Time: %f", length, c$ssl$cipher, duration),
$conn=c,
$n=length,
$identifier=fmt("%s-weak-%d", c$uid, length)
]);
# Examine request lengths based on used cipher...
local min_length_choice: vector of min_length;
if ( (c$ssl$version == "TLSv11") || (c$ssl$version == "TLSv12") ) # tls 1.1+ have different lengths for CBC
min_length_choice = min_lengths_tls11;
else
min_length_choice = min_lengths;
for ( i in min_length_choice )
{
if ( min_length_choice[i]$cipher in c$ssl$cipher )
{
if ( length < min_length_choice[i]$min_length )
{
NOTICE([$note=SSL_Heartbeat_Odd_Length,
$msg=fmt("Heartbeat message smaller than minimum required length. Probable attack. Message length: %d. Required length: %d. Cipher: %s. Cipher match: %s", length, min_length_choice[i]$min_length, c$ssl$cipher, min_length_choice[i]$cipher),
$conn=c,
$n=length,
$identifier=fmt("%s-weak-%d", c$uid, length)
]);
}
break;
}
}
if ( is_orig )
{
if ( c$ssl?$last_responder_heartbeat_request_size )
{
# server originated heartbeat. Ignore & continue
delete c$ssl$last_responder_heartbeat_request_size;
}
else
c$ssl$last_originator_heartbeat_request_size = length;
}
else
{
if ( c$ssl?$last_originator_heartbeat_request_size && c$ssl$last_originator_heartbeat_request_size < length )
{
NOTICE([$note=SSL_Heartbeat_Attack_Success,
$msg=fmt("An encrypted TLS heartbleed attack was probably detected! First packet client record length %d, first packet server record length %d. Time: %f",
c$ssl$last_originator_heartbeat_request_size, length, duration),
$conn=c,
$identifier=c$uid # only throw once per connection
]);
}
else if ( ! c$ssl?$last_originator_heartbeat_request_size )
c$ssl$last_responder_heartbeat_request_size = length;
if ( c$ssl?$last_originator_heartbeat_request_size )
delete c$ssl$last_originator_heartbeat_request_size;
}
}
event ssl_encrypted_data(c: connection, is_orig: bool, content_type: count, length: count)
{
if ( !c?$ssl )
return;
if ( content_type == SSL::HEARTBEAT )
event ssl_encrypted_heartbeat(c, is_orig, length);
else if ( (content_type == SSL::APPLICATION_DATA) && (length > 0) )
{
++c$ssl$enc_appdata_packages;
c$ssl$enc_appdata_bytes += length;
}
}

View file

@ -1,8 +1,9 @@
##! Log information about certificates while attempting to avoid duplicate logging.
##! Log information about certificates while attempting to avoid duplicate
##! logging.
@load base/utils/directions-and-hosts
@load base/protocols/ssl
@load protocols/ssl/cert-hash
@load base/files/x509
module Known;
@ -26,16 +27,16 @@ export {
};
## The certificates whose existence should be logged and tracked.
## Choices are: LOCAL_HOSTS, REMOTE_HOSTS, ALL_HOSTS, NO_HOSTS
## Choices are: LOCAL_HOSTS, REMOTE_HOSTS, ALL_HOSTS, NO_HOSTS.
const cert_tracking = LOCAL_HOSTS &redef;
## The set of all known certificates to store for preventing duplicate
## logging. It can also be used from other scripts to
## logging. It can also be used from other scripts to
## inspect if a certificate has been seen in use. The string value
## in the set is for storing the DER formatted certificate's MD5 hash.
## in the set is for storing the DER formatted certificate' SHA1 hash.
global certs: set[addr, string] &create_expire=1day &synchronized &redef;
## Event that can be handled to access the loggable record as it is sent
## Event that can be handled to access the loggable record as it is sent
## on to the logging framework.
global log_known_certs: event(rec: CertsInfo);
}
@ -45,16 +46,28 @@ event bro_init() &priority=5
Log::create_stream(Known::CERTS_LOG, [$columns=CertsInfo, $ev=log_known_certs]);
}
event x509_certificate(c: connection, is_orig: bool, cert: X509, chain_idx: count, chain_len: count, der_cert: string) &priority=3
event ssl_established(c: connection) &priority=3
{
# Make sure this is the server cert and we have a hash for it.
if ( is_orig || chain_idx != 0 || ! c$ssl?$cert_hash )
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| < 1 ||
! c$ssl$cert_chain[0]?$x509 )
return;
local host = c$id$resp_h;
if ( [host, c$ssl$cert_hash] !in certs && addr_matches_host(host, cert_tracking) )
local fuid = c$ssl$cert_chain_fuids[0];
if ( ! c$ssl$cert_chain[0]?$sha1 )
{
add certs[host, c$ssl$cert_hash];
Reporter::error(fmt("Certificate with fuid %s did not contain sha1 hash when checking for known certs. Aborting",
fuid));
return;
}
local hash = c$ssl$cert_chain[0]$sha1;
local cert = c$ssl$cert_chain[0]$x509$certificate;
local host = c$id$resp_h;
if ( [host, hash] !in certs && addr_matches_host(host, cert_tracking) )
{
add certs[host, hash];
Log::write(Known::CERTS_LOG, [$ts=network_time(), $host=host,
$port_num=c$id$resp_p, $subject=cert$subject,
$issuer_subject=cert$issuer,

View file

@ -0,0 +1,68 @@
##! When this script is loaded, only the host certificates (client and server)
##! will be logged to x509.log. Logging of all other certificates will be suppressed.
@load base/protocols/ssl
@load base/files/x509
module X509;
export {
redef record Info += {
# Logging is suppressed if field is set to F
logcert: bool &default=T;
};
}
# We need both the Info and the fa_file record modified.
# The only instant when we have both, the connection and the
# file available without having to loop is in the file_over_new_connection
# event.
# When that event is raised, the x509 record in f$info (which is the only
# record the logging framework gets) is not yet available. So - we
# have to do this two times, sorry.
# Alternatively, we could place it info Files::Info first - but we would
# still have to copy it.
redef record fa_file += {
logcert: bool &default=T;
};
function host_certs_only(rec: X509::Info): bool
{
return rec$logcert;
}
event bro_init() &priority=2
{
local f = Log::get_filter(X509::LOG, "default");
Log::remove_filter(X509::LOG, "default"); # disable default logging
f$pred=host_certs_only; # and add our predicate
Log::add_filter(X509::LOG, f);
}
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=2
{
if ( ! c?$ssl )
return;
local chain: vector of string;
if ( is_orig )
chain = c$ssl$client_cert_chain_fuids;
else
chain = c$ssl$cert_chain_fuids;
if ( |chain| == 0 )
{
Reporter::warning(fmt("Certificate not in chain? (fuid %s)", f$id));
return;
}
# Check if this is the host certificate
if ( f$id != chain[0] )
f$logcert=F;
}
event x509_certificate(f: fa_file, cert_ref: opaque of x509, cert: X509::Certificate) &priority=2
{
f$info$x509$logcert = f$logcert; # info record available, copy information.
}

View file

@ -16,7 +16,6 @@ export {
}
redef record SSL::Info += {
sha1: string &log &optional;
notary: Response &log &optional;
};
@ -38,14 +37,13 @@ function clear_waitlist(digest: string)
}
}
event x509_certificate(c: connection, is_orig: bool, cert: X509,
chain_idx: count, chain_len: count, der_cert: string)
event ssl_established(c: connection) &priority=3
{
if ( is_orig || chain_idx != 0 || ! c?$ssl )
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 ||
! c$ssl$cert_chain[0]?$sha1 )
return;
local digest = sha1_hash(der_cert);
c$ssl$sha1 = digest;
local digest = c$ssl$cert_chain[0]$sha1;
if ( digest in notary_cache )
{

View file

@ -2,14 +2,14 @@
@load base/frameworks/notice
@load base/protocols/ssl
@load protocols/ssl/cert-hash
module SSL;
export {
redef enum Notice::Type += {
## This notice indicates that the result of validating the certificate
## along with it's full certificate chain was invalid.
## This notice indicates that the result of validating the
## certificate along with its full certificate chain was
## invalid.
Invalid_Server_Cert
};
@ -18,9 +18,9 @@ export {
validation_status: string &log &optional;
};
## MD5 hash values for recently validated certs along with the validation
## status message are kept in this table to avoid constant validation
## everytime the same certificate is seen.
## MD5 hash values for recently validated chains along with the
## validation status message are kept in this table to avoid constant
## validation every time the same certificate chain is seen.
global recently_validated_certs: table[string] of string = table()
&read_expire=5mins &synchronized &redef;
}
@ -28,25 +28,36 @@ export {
event ssl_established(c: connection) &priority=3
{
# If there aren't any certs we can't very well do certificate validation.
if ( ! c$ssl?$cert || ! c$ssl?$cert_chain )
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 ||
! c$ssl$cert_chain[0]?$x509 )
return;
if ( c$ssl?$cert_hash && c$ssl$cert_hash in recently_validated_certs )
local chain_id = join_string_vec(c$ssl$cert_chain_fuids, ".");
local chain: vector of opaque of x509 = vector();
for ( i in c$ssl$cert_chain )
{
c$ssl$validation_status = recently_validated_certs[c$ssl$cert_hash];
if ( c$ssl$cert_chain[i]?$x509 )
chain[i] = c$ssl$cert_chain[i]$x509$handle;
}
if ( chain_id in recently_validated_certs )
{
c$ssl$validation_status = recently_validated_certs[chain_id];
}
else
{
local result = x509_verify(c$ssl$cert, c$ssl$cert_chain, root_certs);
c$ssl$validation_status = x509_err2str(result);
local result = x509_verify(chain, root_certs);
c$ssl$validation_status = result$result_string;
recently_validated_certs[chain_id] = result$result_string;
}
if ( c$ssl$validation_status != "ok" )
{
local message = fmt("SSL certificate validation failed with (%s)", c$ssl$validation_status);
NOTICE([$note=Invalid_Server_Cert, $msg=message,
$sub=c$ssl$subject, $conn=c,
$identifier=cat(c$id$resp_h,c$id$resp_p,c$ssl$validation_status,c$ssl$cert_hash)]);
$identifier=cat(c$id$resp_h,c$id$resp_p,c$ssl$validation_status)]);
}
}

View file

@ -0,0 +1,66 @@
##! Perform OCSP response validation.
@load base/frameworks/notice
@load base/protocols/ssl
module SSL;
export {
redef enum Notice::Type += {
## This indicates that the OCSP response was not deemed
## to be valid.
Invalid_Ocsp_Response
};
redef record Info += {
## Result of ocsp validation for this connection.
ocsp_status: string &log &optional;
## ocsp response as string.
ocsp_response: string &optional;
};
}
# MD5 hash values for recently validated chains along with the OCSP validation
# status are kept in this table to avoid constant validation every time the same
# certificate chain is seen.
global recently_ocsp_validated: table[string] of string = table() &read_expire=5mins;
event ssl_stapled_ocsp(c: connection, is_orig: bool, response: string) &priority=3
{
c$ssl$ocsp_response = response;
}
event ssl_established(c: connection) &priority=3
{
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 || !c$ssl?$ocsp_response )
return;
local chain: vector of opaque of x509 = vector();
for ( i in c$ssl$cert_chain )
{
if ( c$ssl$cert_chain[i]?$x509 )
chain[i] = c$ssl$cert_chain[i]$x509$handle;
}
local reply_id = cat(md5_hash(c$ssl$ocsp_response), join_string_vec(c$ssl$cert_chain_fuids, "."));
if ( reply_id in recently_ocsp_validated )
{
c$ssl$ocsp_status = recently_ocsp_validated[reply_id];
return;
}
local result = x509_ocsp_verify(chain, c$ssl$ocsp_response, root_certs);
c$ssl$ocsp_status = result$result_string;
recently_ocsp_validated[reply_id] = result$result_string;
if( result$result_string != "good" )
{
local message = fmt("OCSP response validation failed with (%s)", result$result_string);
NOTICE([$note=Invalid_Ocsp_Response, $msg=message,
$sub=c$ssl$subject, $conn=c,
$identifier=cat(c$id$resp_h,c$id$resp_p,c$ssl$ocsp_status)]);
}
}

View file

@ -0,0 +1,92 @@
##! Generate notices when SSL/TLS connections use certificates or DH parameters
##! that have potentially unsafe key lengths.
@load base/protocols/ssl
@load base/frameworks/notice
@load base/utils/directions-and-hosts
module SSL;
export {
redef enum Notice::Type += {
## Indicates that a server is using a potentially unsafe key.
Weak_Key,
};
## The category of hosts you would like to be notified about which have
## certificates that are going to be expiring soon. By default, these
## notices will be suppressed by the notice framework for 1 day after a particular
## certificate has had a notice generated. Choices are: LOCAL_HOSTS, REMOTE_HOSTS,
## ALL_HOSTS, NO_HOSTS
const notify_weak_keys = LOCAL_HOSTS &redef;
## The minimal key length in bits that is considered to be safe. Any shorter
## (non-EC) key lengths will trigger the notice.
const notify_minimal_key_length = 2048 &redef;
## Warn if the DH key length is smaller than the certificate key length. This is
## potentially unsafe because it gives a wrong impression of safety due to the
## certificate key length. However, it is very common and cannot be avoided in some
## settings (e.g. with old jave clients).
const notify_dh_length_shorter_cert_length = T &redef;
}
# We check key lengths only for DSA or RSA certificates. For others, we do
# not know what is safe (e.g. EC is safe even with very short key lengths).
event ssl_established(c: connection) &priority=3
{
# If there are no certificates or we are not interested in the server, just return.
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 ||
! addr_matches_host(c$id$resp_h, notify_weak_keys) ||
! c$ssl$cert_chain[0]?$x509 )
return;
local fuid = c$ssl$cert_chain_fuids[0];
local cert = c$ssl$cert_chain[0]$x509$certificate;
if ( !cert?$key_type || !cert?$key_length )
return;
if ( cert$key_type != "dsa" && cert$key_type != "rsa" )
return;
local key_length = cert$key_length;
if ( key_length < notify_minimal_key_length )
NOTICE([$note=Weak_Key,
$msg=fmt("Host uses weak certificate with %d bit key", key_length),
$conn=c, $suppress_for=1day,
$identifier=cat(c$id$resp_h, c$id$resp_h, key_length)
]);
}
event ssl_dh_server_params(c: connection, p: string, q: string, Ys: string) &priority=3
{
if ( ! addr_matches_host(c$id$resp_h, notify_weak_keys) )
return;
local key_length = |p| * 8; # length of the used prime number in bits
if ( key_length < notify_minimal_key_length )
NOTICE([$note=Weak_Key,
$msg=fmt("Host uses weak DH parameters with %d key bits", key_length),
$conn=c, $suppress_for=1day,
$identifier=cat(c$id$resp_h, c$id$resp_p, key_length)
]);
if ( notify_dh_length_shorter_cert_length &&
c?$ssl && c$ssl?$cert_chain && |c$ssl$cert_chain| > 0 && c$ssl$cert_chain[0]?$x509 &&
c$ssl$cert_chain[0]$x509?$certificate && c$ssl$cert_chain[0]$x509$certificate?$key_type &&
(c$ssl$cert_chain[0]$x509$certificate$key_type == "rsa" ||
c$ssl$cert_chain[0]$x509$certificate$key_type == "dsa" ))
{
if ( c$ssl$cert_chain[0]$x509$certificate?$key_length &&
c$ssl$cert_chain[0]$x509$certificate$key_length > key_length )
NOTICE([$note=Weak_Key,
$msg=fmt("DH key length of %d bits is smaller certificate key length of %d bits",
key_length, c$ssl$cert_chain[0]$x509$certificate$key_length),
$conn=c, $suppress_for=1day,
$identifier=cat(c$id$resp_h, c$id$resp_p)
]);
}
}

View file

@ -0,0 +1 @@
Miscellaneous tuning parameters.

View file

@ -0,0 +1,2 @@
Sets various defaults, and prints warning messages to stdout under
certain conditions.

View file

@ -1,2 +1,3 @@
@load ./packet-fragments
@load ./warnings
@load ./warnings
@load ./extracted_file_limits.bro

View file

@ -0,0 +1,4 @@
@load base/files/extract
# 100 MB.
redef FileExtract::default_limit = 104857600;

View file

@ -0,0 +1,4 @@
##! Loading this script will cause all logs to be written
##! out as JSON by default.
redef LogAscii::use_json=T;

View file

@ -1,36 +0,0 @@
##! Load this script to enable global log output to an ElasticSearch database.
module LogElasticSearch;
export {
## An elasticsearch specific rotation interval.
const rotation_interval = 3hr &redef;
## Optionally ignore any :bro:type:`Log::ID` from being sent to
## ElasticSearch with this script.
const excluded_log_ids: set[Log::ID] &redef;
## If you want to explicitly only send certain :bro:type:`Log::ID`
## streams, add them to this set. If the set remains empty, all will
## be sent. The :bro:id:`LogElasticSearch::excluded_log_ids` option will remain in
## effect as well.
const send_logs: set[Log::ID] &redef;
}
event bro_init() &priority=-5
{
if ( server_host == "" )
return;
for ( stream_id in Log::active_streams )
{
if ( stream_id in excluded_log_ids ||
(|send_logs| > 0 && stream_id !in send_logs) )
next;
local filter: Log::Filter = [$name = "default-es",
$writer = Log::WRITER_ELASTICSEARCH,
$interv = LogElasticSearch::rotation_interval];
Log::add_filter(stream_id, filter);
}
}