Update detect-shellshock.bro

Update detect-shellshock.bro

Removed superfluous &synchronized attributes from tables.

# Monitors traffic for Shellshock exploit attempts
# When exploits are seen containing IP addresses and domain values, monitors traffic for 60 minutes watching for endpoints to connect to IP addresses and domains seen in exploit attempts
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/frameworks/notice
@load base/protocols/http
@load base/protocols/dhcp
@load base/protocols/smtp

module CrowdStrike;

export {
  redef enum Notice::Type += {
    Shellshock_DHCP,
    Shellshock_HTTP,
    Shellshock_SMTP,
    Shellshock_Successful_Conn
  };
}

const shellshock_pattern = /((\(|%28)(\)|%29)( |%20)(\{|%7B)|(\{|%7B)(\:|%3A)(\;|%3B))/ &redef;

const shellshock_commands = /wget/ | /curl/;

global shellshock_servers: set[addr] &create_expire=60min;
global shellshock_hosts: set[string] &create_expire=60min;

# function to locate and extract domains seen in shellshock exploit attempts
function find_domain(ss: string)
{
local parts1 = split_all(ss,/[[:space:]]/);
for ( p in parts1 )
  if ( "http" in parts1[p] )
    {
    local parts2 = split_all(parts1[p],/\//);
    local output = parts2[5];
    }

if ( output !in shellshock_hosts )
  add shellshock_hosts[output];
}

# function to locate and extract IP addresses seen in shellshock exploit attempts
function find_ip(ss: string): bool
{
local b = F;
local remote_servers = find_ip_addresses(ss);

if ( |remote_servers| > 0 )
  {
  b = T;
  for ( rs in remote_servers )
    {
    local s = to_addr(remote_servers[rs]);
    if ( s !in shellshock_servers )
      add shellshock_servers[s];
    }
  }

return b;
}

# event to identify shellshock HTTP exploit attempts
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=3
{
if ( ! is_orig ) return;
if ( shellshock_pattern !in value ) return;

# generate a notice of the HTTP exploit attempt
NOTICE([$note=Shellshock_HTTP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock HTTP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",value),
        $identifier=cat(c$id$orig_h,c$id$resp_h,value)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(value) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in value )
  find_domain(value);
}

# event to identify shellshock DHCP exploit attempts
event dhcp_ack(c: connection, msg: dhcp_msg, mask: addr, router: dhcp_router_list, lease: interval, serv_addr: addr, host_name: string)
{
if ( shellshock_pattern !in host_name ) return;

# generate a notice of the DHCP exploit attempt
NOTICE([$note=Shellshock_DHCP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock DHCP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",host_name),
        $identifier=cat(c$id$orig_h,c$id$resp_h,host_name)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(host_name) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in host_name )
  find_domain(host_name);
}

# event to identify endpoints connecting to domains seen in shellshock exploit attempts
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=5
{
if ( name != "HOST" ) return;
if ( value in shellshock_hosts )
  # generate a notice of HTTP connection to domain seen in exploit attempts
  NOTICE([$note=Shellshock_Successful_Conn,
          $conn=c,
          $msg=fmt("Host %s connected to a domain seen in a shellshock exploit", c$id$orig_h),
          $sub=fmt("Domain: %s",value),
          $identifier=cat(c$id$orig_h,value)]);
}

# event to identify shellshock SMTP exploit attempts
event mime_one_header(c: connection, h: mime_header_rec)
{
if ( ! c?$smtp || ! h?$value ) return;
if (  shellshock_pattern !in h$value ) return;

# generate a notice of the SMTP exploit attempt
NOTICE([$note=Shellshock_SMTP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock SMTP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",h$value),
        $identifier=cat(c$id$orig_h,c$id$resp_h,h$value)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(h$value) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in h$value )
  find_domain(h$value);
}

# event to identify endpoints connecting to IP addresses seen in shellshock exploit attempts
event connection_state_remove(c: connection)
{
if ( c$id$resp_h in shellshock_servers )
  # generate a notice of connection to IP address seen in exploit attempts
  NOTICE([$note=Shellshock_Successful_Conn,
          $conn=c,
          $msg=fmt("Host %s connected to an IP address seen in a shellshock exploit", c$id$orig_h),
          $sub=fmt("IP address: %s",c$id$resp_h),
          $identifier=cat(c$id$orig_h,c$id$resp_h)]);
}

# Monitors traffic for Shellshock exploit attempts
# When exploits are seen containing IP addresses and domain values, monitors traffic for 60 minutes watching for endpoints to connect to IP addresses and domains seen in exploit attempts
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/frameworks/notice
@load base/protocols/http
@load base/protocols/dhcp
@load base/protocols/smtp

module CrowdStrike;

export {
  redef enum Notice::Type += {
    Shellshock_DHCP,
    Shellshock_HTTP,
    Shellshock_SMTP,
    Shellshock_Successful_Conn
  };
}

const shellshock_pattern = /((\(|%28)(\)|%29)( |%20)(\{|%7B)|(\{|%7B)(\:|%3A)(\;|%3B))/ &redef;

const shellshock_commands = /wget/ | /curl/;

global shellshock_servers: set[addr] &create_expire=60min &synchronized;
global shellshock_hosts: set[string] &create_expire=60min &synchronized;

# function to locate and extract domains seen in shellshock exploit attempts
function find_domain(ss: string)
{
local parts1 = split_all(ss,/[[:space:]]/);
for ( p in parts1 )
  if ( "http" in parts1[p] )
    {
    local parts2 = split_all(parts1[p],/\//);
    local output = parts2[5];
    }

if ( output !in shellshock_hosts )
  add shellshock_hosts[output];
}

# function to locate and extract IP addresses seen in shellshock exploit attempts
function find_ip(ss: string): bool
{
local b = F;
local remote_servers = find_ip_addresses(ss);

if ( |remote_servers| > 0 )
  {
  b = T;
  for ( rs in remote_servers )
    {
    local s = to_addr(remote_servers[rs]);
    if ( s !in shellshock_servers )
      add shellshock_servers[s];
    }
  }

return b;
}

# event to identify shellshock HTTP exploit attempts
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=3
{
if ( ! is_orig ) return;
if ( shellshock_pattern !in value ) return;

# generate a notice of the HTTP exploit attempt
NOTICE([$note=Shellshock_HTTP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock HTTP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",value),
        $identifier=cat(c$id$orig_h,c$id$resp_h,value)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(value) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in value )
  find_domain(value);
}

# event to identify shellshock DHCP exploit attempts
event dhcp_ack(c: connection, msg: dhcp_msg, mask: addr, router: dhcp_router_list, lease: interval, serv_addr: addr, host_name: string)
{
if ( shellshock_pattern !in host_name ) return;

# generate a notice of the DHCP exploit attempt
NOTICE([$note=Shellshock_DHCP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock DHCP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",host_name),
        $identifier=cat(c$id$orig_h,c$id$resp_h,host_name)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(host_name) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in host_name )
  find_domain(host_name);
}

# event to identify endpoints connecting to domains seen in shellshock exploit attempts
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=5
{
if ( name != "HOST" ) return;
if ( value in shellshock_hosts )
  # generate a notice of HTTP connection to domain seen in exploit attempts
  NOTICE([$note=Shellshock_Successful_Conn,
          $conn=c,
          $msg=fmt("Host %s connected to a domain seen in a shellshock exploit", c$id$orig_h),
          $sub=fmt("Domain: %s",value),
          $identifier=cat(c$id$orig_h,value)]);
}

# event to identify shellshock SMTP exploit attempts
event mime_one_header(c: connection, h: mime_header_rec)
{
if ( ! c?$smtp || ! h?$value ) return;
if (  shellshock_pattern !in h$value ) return;

# generate a notice of the SMTP exploit attempt
NOTICE([$note=Shellshock_SMTP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock SMTP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",h$value),
        $identifier=cat(c$id$orig_h,c$id$resp_h,h$value)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(h$value) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in h$value )
  find_domain(h$value);
}

# event to identify endpoints connecting to IP addresses seen in shellshock exploit attempts
event connection_state_remove(c: connection)
{
if ( c$id$resp_h in shellshock_servers )
  # generate a notice of connection to IP address seen in exploit attempts
  NOTICE([$note=Shellshock_Successful_Conn,
          $conn=c,
          $msg=fmt("Host %s connected to an IP address seen in a shellshock exploit", c$id$orig_h),
          $sub=fmt("IP address: %s",c$id$resp_h),
          $identifier=cat(c$id$orig_h,c$id$resp_h)]);
}


Create conn-udp-icmp.bro

Create conn-udp-icmp.bro

# Support for UDP, ICMP, and non-established TCP connections
# This will only generate Intel matches when a connection is removed from Bro
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/frameworks/intel
@load policy/frameworks/intel/seen/where-locations

event Conn::log_conn(rec: Conn::Info)
{
if ( rec?$proto && ( rec$proto != tcp || ( rec?$history && rec$proto == tcp && "h" !in rec$history ) ) )
  {
  # duration, start_time, addl, and hot are required fields although they are not used by Intel framework
  local dur: interval;
  local history: string;

  if ( rec?$duration )
    dur = rec$duration;
  else dur = 0secs;

  if ( rec?$history )
    history = rec$history;
  else history = "";

  local c = [$uid = rec$uid,$id = rec$id,$history = history,$duration = dur,$start_time = 0,$addl = "",$hot = 0];

  Intel::seen([$host=c$id$orig_h, $conn=c, $where=Conn::IN_ORIG]);
  Intel::seen([$host=c$id$resp_h, $conn=c, $where=Conn::IN_RESP]);
  }
}

error: pathspec 'bro-scripts/intel-extensions/seen/conn-udp-icmp.bro' did not match any file(s) known to git


Create detect-mac-iworm.bro

Create detect-mac-iworm.bro

# Detects endpoints searching Reddit for values related to the Mac.BackDoor.iWorm malware
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/protocols/http
@load base/frameworks/notice

module CrowdStrike;

export {
  redef enum Notice::Type += {
    Mac_BackDoor_iWorm,
  };
}

event http_message_done(c: connection, is_orig: bool, stat: http_message_stat)
{
# stop processing the event if an endpoint is not searching Reddit
if ( ! is_orig || ! c$http?$host || ! c$http?$uri ) return;
if ( c$http$host != "www.reddit.com" ) return;
if ( "?q=" !in c$http$uri ) return;

# locate and extract the Reddit search value
local clean_reddit_uri = find_last(c$http$uri,/\?q\=.*/);
local uri_parts = split_all(clean_reddit_uri,/&/);
local extract_reddit_search = split1(uri_parts[1],/\?q\=/);

# confirm that the search value is 8 bytes of an MD5 hash
if ( |extract_reddit_search[2]| == 16 && extract_reddit_search[2] == /^[A-Fa-f0-9]{16}$/ )
  NOTICE([$note=Mac_BackDoor_iWorm,
          $conn=c,
          $msg=fmt("%s connected to a Reddit page that may be related to Mac.BackDoor.iWorm",c$id$orig_h),
          $sub=c$http$uri,
          $identifier=cat(c$id$orig_h,c$http$uri)]);
}

error: pathspec 'bro-scripts/mac-backdoor-iworm/detect-mac-iworm.bro' did not match any file(s) known to git


Update main.bro

Update main.bro

# Extends functionality of Intel framework in various ways
# Supports three new indicator types: Email subjects, Usernames, and SSL cert subjects
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/frameworks/intel

module Intel;

export {
  redef enum Intel::Type += {
  	CERT_SUBJECT,
    EMAIL_SUBJECT
  };

  redef enum Intel::Where += {
  	RADIUS::IN_USER_NAME,
    FTP::IN_USER_NAME,
    SMTP::IN_SUBJECT,
  	SSL::IN_SERVER_CERT,
  	SSL::IN_CLIENT_CERT
  };
}


rsyslog-invalid-pri.bro: add pri to notice

# Generates a notice and e-mail if syslog messages contain a PRI greater than 191 but less than 1016
#                                                                        fac: (23 but less than 127)
# Vulnerability: http://www.rsyslog.com/remote-syslog-pri-vulnerability/
#
#       To use:
#               1. Add script to configuration local.bro: $ echo '@load rsyslog-invalid-pri.bro' >> $BROPREFIX/share/bro/site/local.bro
#               2. Copy script to $BROPREFIX/share/bro/site
#               3. $ broctl check && broctl install && broctl restart
# Testing:
# mausezahn -t syslog severity=7,facility=24,host=mausezahn -P "Invalid PRI message test" -B 10.1.1.100
#
# Facility: Divide the PRI number by 8.
# 191/8 = 23
#
# Severity:  multiply facility by 8 and subtract from PRI
# 191 - (23 * 8 ) = 7
#
#   PRI = Facility 23 and Priority (7)

# Possible combinations:
# for pri in {1..1016}; do FAC="$((pri/8))"; echo -e -n "PRI:$pri Fac:$FAC Sev:$((pri - ($FAC * 8 )))\n"; done

@load base/frameworks/notice

export {
        redef enum Notice::Type += {
                SYSLOG::Invalid_PRI
        };

        # List your NTP servers here
        const syslog_servers: set[addr] = {
	10.1.1.100
        } &redef;

}

redef Notice::emailed_types += {
                SYSLOG::Invalid_PRI
};

event syslog_message(c: connection, facility: count, severity: count, msg: string)
        {

        if ( c$id$resp_h !in syslog_servers )
                return;

	local pri = (facility * 8) + severity;

        if (facility > 23 && facility < 127)
                {
                NOTICE([$note=SYSLOG::Invalid_PRI,
		$msg=fmt("Syslog message with invalid PRI of %d", pri),
                $conn=c,
                $identifier=cat(c$id$orig_h,c$id$resp_h),
                $suppress_for=1day]);
                }
        }

# Generates a notice and e-mail if syslog messages contain a PRI greater than 191 but less than 1016
#                                                                        fac: (23 but less than 127)
# Vulnerability: http://www.rsyslog.com/remote-syslog-pri-vulnerability/
#
#       To use:
#               1. Add script to configuration local.bro: $ echo '@load rsyslog-invalid-pri.bro' >> $BROPREFIX/share/bro/site/local.bro
#               2. Copy script to $BROPREFIX/share/bro/site
#               3. $ broctl check && broctl install && broctl restart
# Testing:
# mausezahn -t syslog severity=7,facility=24,host=mausezahn -P "Invalid PRI message test" -B 10.1.1.100
#
# Facility: Divide the PRI number by 8.
# 191/8 = 23
#
# Severity:  multiply facility by 8 and subtract from PRI
# 191 - (23 * 8 ) = 7
#
#   PRI = Facility 23 and Priority (7)

# Possible combinations:
# for pri in {1..1016}; do FAC="$((pri/8))"; echo -e -n "PRI:$pri Fac:$FAC Sev:$((pri - ($FAC * 8 )))\n"; done

@load base/frameworks/notice

export {
        redef enum Notice::Type += {
                SYSLOG::Invalid_PRI
        };

        # List your NTP servers here
        const syslog_servers: set[addr] = {
	10.1.1.100
        } &redef;

}

redef Notice::emailed_types += {
                SYSLOG::Invalid_PRI
};

event syslog_message(c: connection, facility: count, severity: count, msg: string)
        {

        if ( c$id$resp_h !in syslog_servers )
                return;

        if (facility > 23 && facility < 127)
                {
                NOTICE([$note=SYSLOG::Invalid_PRI,
                $msg=fmt("Syslog message with invalid PRI from %s to %s", c$id$orig_h, c$id$resp_h),
                $conn=c,
                $identifier=cat(c$id$orig_h,c$id$resp_h),
                $suppress_for=1day]);
                }
        }


Added torlist_location

Added torlist_location

# Log Tor connections based on IP addresses along with Tor node metadata
# Based on data collected from torstatus.blutmagie.de
# CrowdStrike 2015
# josh.liburdi@crowdstrike.com
# @jshlbrd

module Tor;

export {
	redef enum Log::ID += { TOR::LOG };

	type Info: record {
	## Timestamp for when the event happened.
	ts:               time 	&log;
	## Unique ID for the connection.
	uid:              string &log;
	## The connection's 4-tuple of endpoint addresses/ports.
	id:               conn_id &log;
	## Tor node IP address.
	tor_ip:           addr &log &optional;
	## Tor node router name.
	router_name:      string &log &optional;
	## Tor node host name.
	host_name:        string &log &optional;
	## Tor node platform / version number.
	platform:         string &log &optional;
	## Tor node country code location.
	country_code:     string &log &optional;
	## Tor node bandwidth (in KB/s).
	bandwidth:        count	&log &optional;
	## Tor node estimated uptime.
	uptime:           time	&log &optional;
	## Tor node router port.
	router_port:      count	&log &optional;
	## Tor node directory port.
	directory_port:   count	&log &optional;
	## Tor node auth flag.
	auth_flag:        bool	&log &optional;
	## Tor node exit flag.
	exit_flag:        bool	&log &optional;
	## Tor node fast flag.
	fast_flag:        bool	&log &optional;
	## Tor node guard flag.
	guard_flag:       bool	&log &optional;
	## Tor node named flag.
	named_flag:       bool	&log &optional;
	## Tor node stable flag.
	stable_flag:      bool	&log &optional;
	## Tor node running flag.
	running_flag:     bool	&log &optional;
	## Tor node valid flag.
	valid_flag:       bool	&log &optional;
	## Tor node v2dir flag.
	v2dir_flag:       bool	&log &optional;
	## Tor node hibernating flag.
	hibernating_flag: bool	&log &optional;
	## Tor node bad exit flag.
	bad_exit_flag:    bool	&log &optional;
	};

	## Event that can be handled to access the Tor record as it is sent on
	## to the logging framework.
	global log_tor: event(rec: Info);
}

redef record connection += {
	tor: Info &optional;
};

type tor_idx: record {
	tor_ip:	addr;
};

type tor_table: record {
	router_name:      string;
	country_code:     string;
	bandwidth:        count;
	uptime:           double;
	host_name:        string;
	router_port:      count;
	directory_port:   string;
	auth_flag:        count;
	exit_flag:        count;
	fast_flag:        count;
	guard_flag:       count;
	named_flag:       count;
	stable_flag:      count;
	running_flag:     count;
	valid_flag:       count;
	v2dir_flag:       count;
	platform:         string;
	hibernating_flag: count;
	bad_exit_flag:    count;
};

global torlist: table[addr] of tor_table = table();
const torlist_location = "bro-tor.txt";

# Create the Tor log stream and load the Tor list
event bro_init()
{
Log::create_stream(TOR::LOG, [$columns=Info, $ev=log_tor]);
Input::add_table([$source=torlist_location, $name="torlist", $idx=tor_idx, $val=tor_table, $destination=torlist, $mode=Input::REREAD]);
}

# Function to establish a Tor info record
function set_session(c: connection)
{
if ( ! c?$tor )
	{
	add c$service["tor"];
	c$tor = [$ts=network_time(),$id=c$id,$uid=c$uid];
	}
}

# Function to convert blutmagie Tor flags from count to bool
function convert_flag(flag: count): bool
{
if ( flag == 1 )
	return T;
else return F;
}

# Function to set data in the Tor info record
function set_data(c: connection, tor_ip: addr)
{
c$tor$tor_ip = tor_ip;
if ( torlist[tor_ip]?$router_name )
	c$tor$router_name = torlist[tor_ip]$router_name;
if ( torlist[tor_ip]?$host_name )
	c$tor$host_name = torlist[tor_ip]$host_name;
if ( torlist[tor_ip]?$platform )
	c$tor$platform = torlist[tor_ip]$platform;
if ( torlist[tor_ip]?$country_code )
	c$tor$country_code = torlist[tor_ip]$country_code;
if ( torlist[tor_ip]?$bandwidth )
	c$tor$bandwidth = torlist[tor_ip]$bandwidth;

if ( torlist[tor_ip]?$uptime )
	{
	# Uptime is recorded by hour, so we need to convert it to seconds
	local uptime_hr = torlist[tor_ip]$uptime;
	local uptime_time = ( uptime_hr * 3600 );
	c$tor$uptime = double_to_time(uptime_time);
	}

if ( torlist[tor_ip]?$router_port )
	c$tor$router_port = torlist[tor_ip]$router_port;
if ( torlist[tor_ip]?$directory_port )
	if ( torlist[tor_ip]$directory_port != "None" )
		c$tor$directory_port = to_count(torlist[tor_ip]$directory_port);
if ( torlist[tor_ip]?$auth_flag )
	c$tor$auth_flag = convert_flag(torlist[tor_ip]$auth_flag);
if ( torlist[tor_ip]?$exit_flag )
	c$tor$exit_flag = convert_flag(torlist[tor_ip]$exit_flag);
if ( torlist[tor_ip]?$fast_flag )
	c$tor$fast_flag = convert_flag(torlist[tor_ip]$fast_flag);
if ( torlist[tor_ip]?$guard_flag )
	c$tor$guard_flag = convert_flag(torlist[tor_ip]$guard_flag);
if ( torlist[tor_ip]?$named_flag )
	c$tor$named_flag = convert_flag(torlist[tor_ip]$named_flag);
if ( torlist[tor_ip]?$stable_flag )
	c$tor$stable_flag = convert_flag(torlist[tor_ip]$stable_flag);
if ( torlist[tor_ip]?$running_flag )
	c$tor$running_flag = convert_flag(torlist[tor_ip]$running_flag);
if ( torlist[tor_ip]?$valid_flag )
	c$tor$valid_flag = convert_flag(torlist[tor_ip]$valid_flag);
if ( torlist[tor_ip]?$v2dir_flag )
	c$tor$v2dir_flag = convert_flag(torlist[tor_ip]$v2dir_flag);
if ( torlist[tor_ip]?$hibernating_flag )
	c$tor$hibernating_flag = convert_flag(torlist[tor_ip]$hibernating_flag);
if ( torlist[tor_ip]?$bad_exit_flag )
	c$tor$bad_exit_flag = convert_flag(torlist[tor_ip]$bad_exit_flag);
}

# Generate reporter message when the Tor list is updated
event Input::end_of_data(name: string, source: string)
{
local msg = fmt("Tor list updated at %s",network_time());
Log::write(Reporter::LOG, [$ts=network_time(), $level=Reporter::INFO, $message=msg]);
}

# Check each new connection for an IP address in the Tor list
event new_connection(c: connection )
{
if ( c$id$orig_h in torlist )
	{
	set_session(c);
	set_data(c,c$id$orig_h);
	}
else if ( c$id$resp_h in torlist )
	{
	set_session(c);
	set_data(c,c$id$resp_h);
	}
}

# Generate the tor.log for each Tor connection
event connection_state_remove(c: connection)
{
if ( c?$tor )
	{
	Log::write(TOR::LOG, c$tor);
	}
}

# Log Tor connections based on IP addresses along with Tor node metadata
# Based on data collected from torstatus.blutmagie.de
# CrowdStrike 2015
# josh.liburdi@crowdstrike.com
# @jshlbrd

module Tor;

export {
	redef enum Log::ID += { TOR::LOG };

	type Info: record {
	## Timestamp for when the event happened.
	ts:               time 	&log;
	## Unique ID for the connection.
	uid:              string &log;
	## The connection's 4-tuple of endpoint addresses/ports.
	id:               conn_id &log;
	## Tor node IP address.
	tor_ip:           addr &log &optional;
	## Tor node router name.
	router_name:      string &log &optional;
	## Tor node host name.
	host_name:        string &log &optional;
	## Tor node platform / version number.
	platform:         string &log &optional;
	## Tor node country code location.
	country_code:     string &log &optional;
	## Tor node bandwidth (in KB/s).
	bandwidth:        count	&log &optional;
	## Tor node estimated uptime.
	uptime:           time	&log &optional;
	## Tor node router port.
	router_port:      count	&log &optional;
	## Tor node directory port.
	directory_port:   count	&log &optional;
	## Tor node auth flag.
	auth_flag:        bool	&log &optional;
	## Tor node exit flag.
	exit_flag:        bool	&log &optional;
	## Tor node fast flag.
	fast_flag:        bool	&log &optional;
	## Tor node guard flag.
	guard_flag:       bool	&log &optional;
	## Tor node named flag.
	named_flag:       bool	&log &optional;
	## Tor node stable flag.
	stable_flag:      bool	&log &optional;
	## Tor node running flag.
	running_flag:     bool	&log &optional;
	## Tor node valid flag.
	valid_flag:       bool	&log &optional;
	## Tor node v2dir flag.
	v2dir_flag:       bool	&log &optional;
	## Tor node hibernating flag.
	hibernating_flag: bool	&log &optional;
	## Tor node bad exit flag.
	bad_exit_flag:    bool	&log &optional;
	};

	## Event that can be handled to access the Tor record as it is sent on
	## to the logging framework.
	global log_tor: event(rec: Info);
}

redef record connection += {
	tor: Info &optional;
};

type tor_idx: record {
	tor_ip:	addr;
};

type tor_table: record {
	router_name:      string;
	country_code:     string;
	bandwidth:        count;
	uptime:           double;
	host_name:        string;
	router_port:      count;
	directory_port:   string;
	auth_flag:        count;
	exit_flag:        count;
	fast_flag:        count;
	guard_flag:       count;
	named_flag:       count;
	stable_flag:      count;
	running_flag:     count;
	valid_flag:       count;
	v2dir_flag:       count;
	platform:         string;
	hibernating_flag: count;
	bad_exit_flag:    count;
};

global torlist: table[addr] of tor_table = table();

# Create the Tor log stream and load the Tor list
event bro_init()
{
Log::create_stream(TOR::LOG, [$columns=Info, $ev=log_tor]);
Input::add_table([$source=torlist_location, $name="torlist", $idx=tor_idx, $val=tor_table, $destination=torlist, $mode=Input::REREAD]);
}

# Function to establish a Tor info record
function set_session(c: connection)
{
if ( ! c?$tor )
	{
	add c$service["tor"];
	c$tor = [$ts=network_time(),$id=c$id,$uid=c$uid];
	}
}

# Function to convert blutmagie Tor flags from count to bool
function convert_flag(flag: count): bool
{
if ( flag == 1 )
	return T;
else return F;
}

# Function to set data in the Tor info record
function set_data(c: connection, tor_ip: addr)
{
c$tor$tor_ip = tor_ip;
if ( torlist[tor_ip]?$router_name )
	c$tor$router_name = torlist[tor_ip]$router_name;
if ( torlist[tor_ip]?$host_name )
	c$tor$host_name = torlist[tor_ip]$host_name;
if ( torlist[tor_ip]?$platform )
	c$tor$platform = torlist[tor_ip]$platform;
if ( torlist[tor_ip]?$country_code )
	c$tor$country_code = torlist[tor_ip]$country_code;
if ( torlist[tor_ip]?$bandwidth )
	c$tor$bandwidth = torlist[tor_ip]$bandwidth;

if ( torlist[tor_ip]?$uptime )
	{
	# Uptime is recorded by hour, so we need to convert it to seconds
	local uptime_hr = torlist[tor_ip]$uptime;
	local uptime_time = ( uptime_hr * 3600 );
	c$tor$uptime = double_to_time(uptime_time);
	}

if ( torlist[tor_ip]?$router_port )
	c$tor$router_port = torlist[tor_ip]$router_port;
if ( torlist[tor_ip]?$directory_port )
	if ( torlist[tor_ip]$directory_port != "None" )
		c$tor$directory_port = to_count(torlist[tor_ip]$directory_port);
if ( torlist[tor_ip]?$auth_flag )
	c$tor$auth_flag = convert_flag(torlist[tor_ip]$auth_flag);
if ( torlist[tor_ip]?$exit_flag )
	c$tor$exit_flag = convert_flag(torlist[tor_ip]$exit_flag);
if ( torlist[tor_ip]?$fast_flag )
	c$tor$fast_flag = convert_flag(torlist[tor_ip]$fast_flag);
if ( torlist[tor_ip]?$guard_flag )
	c$tor$guard_flag = convert_flag(torlist[tor_ip]$guard_flag);
if ( torlist[tor_ip]?$named_flag )
	c$tor$named_flag = convert_flag(torlist[tor_ip]$named_flag);
if ( torlist[tor_ip]?$stable_flag )
	c$tor$stable_flag = convert_flag(torlist[tor_ip]$stable_flag);
if ( torlist[tor_ip]?$running_flag )
	c$tor$running_flag = convert_flag(torlist[tor_ip]$running_flag);
if ( torlist[tor_ip]?$valid_flag )
	c$tor$valid_flag = convert_flag(torlist[tor_ip]$valid_flag);
if ( torlist[tor_ip]?$v2dir_flag )
	c$tor$v2dir_flag = convert_flag(torlist[tor_ip]$v2dir_flag);
if ( torlist[tor_ip]?$hibernating_flag )
	c$tor$hibernating_flag = convert_flag(torlist[tor_ip]$hibernating_flag);
if ( torlist[tor_ip]?$bad_exit_flag )
	c$tor$bad_exit_flag = convert_flag(torlist[tor_ip]$bad_exit_flag);
}

# Generate reporter message when the Tor list is updated
event Input::end_of_data(name: string, source: string)
{
local msg = fmt("Tor list updated at %s",network_time());
Log::write(Reporter::LOG, [$ts=network_time(), $level=Reporter::INFO, $message=msg]);
}

# Check each new connection for an IP address in the Tor list
event new_connection(c: connection )
{
if ( c$id$orig_h in torlist )
	{
	set_session(c);
	set_data(c,c$id$orig_h);
	}
else if ( c$id$resp_h in torlist )
	{
	set_session(c);
	set_data(c,c$id$resp_h);
	}
}

# Generate the tor.log for each Tor connection
event connection_state_remove(c: connection)
{
if ( c?$tor )
	{
	Log::write(TOR::LOG, c$tor);
	}
}


Update __load__.bro

Update __load__.bro

Moved add-tor to bro-scripts/tor directory


Create add-tor.bro

Create add-tor.bro

module Tor;

export {
        redef record Conn::Info += {
                found_tor: bool &log &default=F;
        };
}

event connection_state_remove (c: connection)
{
if ( c?$tor )
        c$conn$found_tor = T;
}

error: pathspec 'bro-scripts/tor/add-tor.bro' did not match any file(s) known to git


Create add-tor.bro

Create add-tor.bro

error: pathspec 'bro-scripts/tor/policy/add-tor.bro' did not match any file(s) known to git


Update main.bro

Update main.bro

# Extends functionality of Intel framework in various ways
# Supports three new indicator types: Email subjects, Usernames, and SSL cert subjects
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/frameworks/intel

module Intel;

export {
  redef enum Intel::Type += {
    CERT_SUBJECT,
    EMAIL_SUBJECT
  };

  redef enum Intel::Where += {
    RADIUS::IN_USER_NAME,
    FTP::IN_USER_NAME,
    SMTP::IN_SUBJECT,
    SSL::IN_SERVER_CERT,
    SSL::IN_CLIENT_CERT
  };
}


Create detect-ms15-034.bro

Create detect-ms15-034.bro

# Detects MS15-034 vulnerabilities by inspecting inbound HTTP requests and web server responses
# Any generated notices should have their sub value inspected for the appropriate exploitable RANGE values
# CrowdStrike 2015
# josh.liburdi@crowdstrike.com

@load base/frameworks/notice
@load base/protocols/http

module CrowdStrike;

export {
        redef enum Notice::Type += {
                MS15034_Vulnerability
        };
}

# RANGE values seen in each connection are stored here
global ranges: table[string] of string;

# RANGE values are extracted from client HTTP headers if the web server has a private IP address or if the web server is within the local network
event http_header(c: connection, is_orig: bool, name: string, value: string)
{
if ( ! is_orig )
        return;

if ( Site::is_local_addr(c$id$resp_h) == T || Site::is_private_addr(c$id$resp_h) == T )
        if ( name == "RANGE" )
                ranges[c$uid] = value;
}

# If an HTTP reply is seen for connections in the ranges table, then the web server's response code is checked for a match with the vulnerability
# A notice is generated that contains the client RANGE request
event http_reply(c: connection, version: string, code: count, reason: string)
{
if ( c$uid in ranges )
        {
        if ( code == 416 )
                {
                local sub_msg = fmt("Range used: %s",ranges[c$uid]);
                NOTICE([$note=MS15034_Vulnerability,
                        $conn=c,
                        $msg=fmt("%s may be vulnerable to MS15-034",c$id$resp_h),
                        $sub=sub_msg,
                        $identifier=cat(c$id$resp_h,ranges[c$uid])]);
                }

        delete ranges[c$uid];
        }
}

# The ranges table is cleaned up as connections are dropped from Bro
event connection_state_remove(c: connection)
{
if ( c?$http )
        if ( c$uid in ranges )
                delete ranges[c$uid];
}

error: pathspec 'bro-scripts/msb/detect-ms15-034.bro' did not match any file(s) known to git


more bro aliases

more bro aliases

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/sprax/dotFiles.git/'


Create detect-rogue-dns.bro

Create detect-rogue-dns.bro

@load base/protocols/dns
@load base/frameworks/notice
@load base/frameworks/input

module CrowdStrike::Hurricane_Panda;

export {
  redef enum Notice::Type += {
    HE_Request,
    HE_Successful_Conn
  };
}

# File generated by scrape-alexa.py
const alexa_file = "" &redef;
# Table to store list of domains in file above
global alexa_table: set[string];
# Record for domains in file above
type alexa_idx: record {
  alexa: string;
};

const he_nets: set[subnet] = {
  216.218.130.2,
  216.66.1.2,
  216.66.80.18,
  216.218.132.2,
  216.218.131.2
} &redef;

# Pattern used to identify subdomains
const subdomains =  /^d?ns[0-9]*\./    |
                    /^smtp[0-9]*\./    |
                    /^mail[0-9]*\./    |
                    /^pop[0-9]*\./     |
                    /^imap[0-9]*\./    |
                    /^www[0-9]*\./     |
                    /^ftp[0-9]*\./     |
                    /^img[0-9]*\./     |
                    /^images?[0-9]*\./ |
                    /^search[0-9]*\./  |
                    /^nginx[0-9]*\./ &redef;

# Container to store IP address answers from Hurricane Electric queries
# Each entry expires 5 minutes after the last time it was written
global he_answers: set[addr] &write_expire=5min;

# Pulls data from scrape-alexa.py output (alexa_file)
event bro_init()
{
Input::add_table([$source=alexa_file,
                  $name="alexa_domains",
                  $idx=alexa_idx,
                  $destination=alexa_table,
                  $mode=Input::REREAD]);
}

event DNS::log_dns(rec: DNS::Info)
{
# Do not process the event if no query exists or if the query is not being resolved by Hurricane Electric
if ( ! rec?$query ) return;
if ( rec$id$resp_h !in he_nets ) return;

# If necessary, clean the query so that it can be found in the list of Alexa domains
local query = rec$query;
if ( subdomains in query )
  query = sub(rec$query,subdomains,"");

# Check if the query is in the list of Alexa domains
# If it is, then this activity is suspicious and should be investigated
if ( query in alexa_table )
  {
  # Prepare the sub-message for the notice
  # Include the domain queried in the sub-message
  local sub_msg = fmt("Query: %s",query);

  # If the query was answered, include the answers in the sub-message
  if ( rec?$answers )
    {
    sub_msg += fmt(" %s: ", rec$total_answers > 1 ? "Answers":"Answer");

    for ( ans in rec$answers )
      {
      # If an answers value is an IP address, store it for later processing
      if ( is_valid_ip(rec$answers[ans]) == T )
        add he_answers[to_addr(rec$answers[ans])];
      sub_msg += fmt("%s, ", rec$answers[ans]);
      }

    # Clean the sub-message
    sub_msg = cut_tail(sub_msg,2);
    }

  # Generate the notice
  # Includes the connection flow, host intiating the lookup, domain queried, and query answers (if available)
  NOTICE([$note=HE_Request,
          $msg=fmt("%s made a suspicious DNS lookup to Hurricane Electric", rec$id$orig_h),
          $sub=sub_msg,
          $id=rec$id,
          $uid=rec$uid,
          $identifier=cat(rec$id$orig_h,rec$query)]);
  }
}

event connection_state_remove(c: connection)
{
# Check if a host connected to an IP address seen in an answer from Hurricane Electric
if ( c$id$resp_h in he_answers )
  NOTICE([$note=HE_Successful_Conn,
          $conn=c,
          $msg=fmt("%s connected to a suspicious server resolved by Hurricane Electric", c$id$orig_h),
          $identifier=cat(c$id$orig_h,c$id$resp_h)]);
}

error: pathspec 'bro-scripts/adversaries/hurricane-panda/rogue-dns/dynamic/detect-rogue-dns.bro' did not match any file(s) known to git


Update detect-rogue-dns.bro

Update detect-rogue-dns.bro

Added comments and location for alexa_file

# Detects a Hurricane Panda tactic of using Hurricane Electric to resolve commonly accessed websites
# Alerts when a domain in the Alexa top 500 is resolved via Hurricane Electric and/or when a host connects to an IP in the DNS response
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/protocols/dns
@load base/frameworks/notice
@load base/frameworks/input

module CrowdStrike::Hurricane_Panda;

export {
  redef enum Notice::Type += {
    HE_Request,
    HE_Successful_Conn
  };
}

# File generated by scrape-alexa.py
const alexa_file = "./alexa_domains.txt" &redef;
# Table to store list of domains in file above
global alexa_table: set[string];
# Record for domains in file above
type alexa_idx: record {
  alexa: string;
};

const he_nets: set[subnet] = {
  216.218.130.2,
  216.66.1.2,
  216.66.80.18,
  216.218.132.2,
  216.218.131.2
} &redef;

# Pattern used to identify subdomains
const subdomains =  /^d?ns[0-9]*\./    |
                    /^smtp[0-9]*\./    |
                    /^mail[0-9]*\./    |
                    /^pop[0-9]*\./     |
                    /^imap[0-9]*\./    |
                    /^www[0-9]*\./     |
                    /^ftp[0-9]*\./     |
                    /^img[0-9]*\./     |
                    /^images?[0-9]*\./ |
                    /^search[0-9]*\./  |
                    /^nginx[0-9]*\./ &redef;

# Container to store IP address answers from Hurricane Electric queries
# Each entry expires 5 minutes after the last time it was written
global he_answers: set[addr] &write_expire=5min;

# Pulls data from scrape-alexa.py output (alexa_file)
event bro_init()
{
Input::add_table([$source=alexa_file,
                  $name="alexa_domains",
                  $idx=alexa_idx,
                  $destination=alexa_table,
                  $mode=Input::REREAD]);
}

event DNS::log_dns(rec: DNS::Info)
{
# Do not process the event if no query exists or if the query is not being resolved by Hurricane Electric
if ( ! rec?$query ) return;
if ( rec$id$resp_h !in he_nets ) return;

# If necessary, clean the query so that it can be found in the list of Alexa domains
local query = rec$query;
if ( subdomains in query )
  query = sub(rec$query,subdomains,"");

# Check if the query is in the list of Alexa domains
# If it is, then this activity is suspicious and should be investigated
if ( query in alexa_table )
  {
  # Prepare the sub-message for the notice
  # Include the domain queried in the sub-message
  local sub_msg = fmt("Query: %s",query);

  # If the query was answered, include the answers in the sub-message
  if ( rec?$answers )
    {
    sub_msg += fmt(" %s: ", rec$total_answers > 1 ? "Answers":"Answer");

    for ( ans in rec$answers )
      {
      # If an answers value is an IP address, store it for later processing
      if ( is_valid_ip(rec$answers[ans]) == T )
        add he_answers[to_addr(rec$answers[ans])];
      sub_msg += fmt("%s, ", rec$answers[ans]);
      }

    # Clean the sub-message
    sub_msg = cut_tail(sub_msg,2);
    }

  # Generate the notice
  # Includes the connection flow, host intiating the lookup, domain queried, and query answers (if available)
  NOTICE([$note=HE_Request,
          $msg=fmt("%s made a suspicious DNS lookup to Hurricane Electric", rec$id$orig_h),
          $sub=sub_msg,
          $id=rec$id,
          $uid=rec$uid,
          $identifier=cat(rec$id$orig_h,rec$query)]);
  }
}

event connection_state_remove(c: connection)
{
# Check if a host connected to an IP address seen in an answer from Hurricane Electric
if ( c$id$resp_h in he_answers )
  NOTICE([$note=HE_Successful_Conn,
          $conn=c,
          $msg=fmt("%s connected to a suspicious server resolved by Hurricane Electric", c$id$orig_h),
          $identifier=cat(c$id$orig_h,c$id$resp_h)]);
}

@load base/protocols/dns
@load base/frameworks/notice
@load base/frameworks/input

module CrowdStrike::Hurricane_Panda;

export {
  redef enum Notice::Type += {
    HE_Request,
    HE_Successful_Conn
  };
}

# File generated by scrape-alexa.py
const alexa_file = "" &redef;
# Table to store list of domains in file above
global alexa_table: set[string];
# Record for domains in file above
type alexa_idx: record {
  alexa: string;
};

const he_nets: set[subnet] = {
  216.218.130.2,
  216.66.1.2,
  216.66.80.18,
  216.218.132.2,
  216.218.131.2
} &redef;

# Pattern used to identify subdomains
const subdomains =  /^d?ns[0-9]*\./    |
                    /^smtp[0-9]*\./    |
                    /^mail[0-9]*\./    |
                    /^pop[0-9]*\./     |
                    /^imap[0-9]*\./    |
                    /^www[0-9]*\./     |
                    /^ftp[0-9]*\./     |
                    /^img[0-9]*\./     |
                    /^images?[0-9]*\./ |
                    /^search[0-9]*\./  |
                    /^nginx[0-9]*\./ &redef;

# Container to store IP address answers from Hurricane Electric queries
# Each entry expires 5 minutes after the last time it was written
global he_answers: set[addr] &write_expire=5min;

# Pulls data from scrape-alexa.py output (alexa_file)
event bro_init()
{
Input::add_table([$source=alexa_file,
                  $name="alexa_domains",
                  $idx=alexa_idx,
                  $destination=alexa_table,
                  $mode=Input::REREAD]);
}

event DNS::log_dns(rec: DNS::Info)
{
# Do not process the event if no query exists or if the query is not being resolved by Hurricane Electric
if ( ! rec?$query ) return;
if ( rec$id$resp_h !in he_nets ) return;

# If necessary, clean the query so that it can be found in the list of Alexa domains
local query = rec$query;
if ( subdomains in query )
  query = sub(rec$query,subdomains,"");

# Check if the query is in the list of Alexa domains
# If it is, then this activity is suspicious and should be investigated
if ( query in alexa_table )
  {
  # Prepare the sub-message for the notice
  # Include the domain queried in the sub-message
  local sub_msg = fmt("Query: %s",query);

  # If the query was answered, include the answers in the sub-message
  if ( rec?$answers )
    {
    sub_msg += fmt(" %s: ", rec$total_answers > 1 ? "Answers":"Answer");

    for ( ans in rec$answers )
      {
      # If an answers value is an IP address, store it for later processing
      if ( is_valid_ip(rec$answers[ans]) == T )
        add he_answers[to_addr(rec$answers[ans])];
      sub_msg += fmt("%s, ", rec$answers[ans]);
      }

    # Clean the sub-message
    sub_msg = cut_tail(sub_msg,2);
    }

  # Generate the notice
  # Includes the connection flow, host intiating the lookup, domain queried, and query answers (if available)
  NOTICE([$note=HE_Request,
          $msg=fmt("%s made a suspicious DNS lookup to Hurricane Electric", rec$id$orig_h),
          $sub=sub_msg,
          $id=rec$id,
          $uid=rec$uid,
          $identifier=cat(rec$id$orig_h,rec$query)]);
  }
}

event connection_state_remove(c: connection)
{
# Check if a host connected to an IP address seen in an answer from Hurricane Electric
if ( c$id$resp_h in he_answers )
  NOTICE([$note=HE_Successful_Conn,
          $conn=c,
          $msg=fmt("%s connected to a suspicious server resolved by Hurricane Electric", c$id$orig_h),
          $identifier=cat(c$id$orig_h,c$id$resp_h)]);
}


Added SMTP support

Added SMTP support

Added detection for Shellshock exploits over SMTP.

# Monitors traffic for Shellshock exploit attempts
# When exploits are seen containing IP addresses and domain values, monitors traffic for 60 minutes watching for endpoints to connect to IP addresses and domains seen in exploit attempts
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/frameworks/notice
@load base/protocols/http
@load base/protocols/dhcp
@load base/protocols/smtp

module CrowdStrike;

export {
  redef enum Notice::Type += {
    Shellshock_DHCP,
    Shellshock_HTTP,
    Shellshock_SMTP,
    Shellshock_Successful_Conn
  };
}

const shellshock_pattern = /((\(|%28)(\)|%29)( |%20)(\{|%7B)|(\{|%7B)(\:|%3A)(\;|%3B))/ &redef;

const shellshock_commands = /wget/ | /curl/;

global shellshock_servers: set[addr] &create_expire=60min &synchronized;
global shellshock_hosts: set[string] &create_expire=60min &synchronized;

# function to locate and extract domains seen in shellshock exploit attempts
function find_domain(ss: string)
{
local parts1 = split_all(ss,/[[:space:]]/);
for ( p in parts1 )
  if ( "http" in parts1[p] )
    {
    local parts2 = split_all(parts1[p],/\//);
    local output = parts2[5];
    }

if ( output !in shellshock_hosts )
  add shellshock_hosts[output];
}

# function to locate and extract IP addresses seen in shellshock exploit attempts
function find_ip(ss: string): bool
{
local b = F;
local remote_servers = find_ip_addresses(ss);

if ( |remote_servers| > 0 )
  {
  b = T;
  for ( rs in remote_servers )
    {
    local s = to_addr(remote_servers[rs]);
    if ( s !in shellshock_servers )
      add shellshock_servers[s];
    }
  }

return b;
}

# event to identify shellshock HTTP exploit attempts
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=3
{
if ( ! is_orig ) return;
if ( shellshock_pattern !in value ) return;

# generate a notice of the HTTP exploit attempt
NOTICE([$note=Shellshock_HTTP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock HTTP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",value),
        $identifier=cat(c$id$orig_h,c$id$resp_h,value)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(value) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in value )
  find_domain(value);
}

# event to identify shellshock DHCP exploit attempts
event dhcp_ack(c: connection, msg: dhcp_msg, mask: addr, router: dhcp_router_list, lease: interval, serv_addr: addr, host_name: string)
{
if ( shellshock_pattern !in host_name ) return;

# generate a notice of the DHCP exploit attempt
NOTICE([$note=Shellshock_DHCP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock DHCP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",host_name),
        $identifier=cat(c$id$orig_h,c$id$resp_h,host_name)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(host_name) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in host_name )
  find_domain(host_name);
}

# event to identify endpoints connecting to domains seen in shellshock exploit attempts
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=5
{
if ( name != "HOST" ) return;
if ( value in shellshock_hosts )
  # generate a notice of HTTP connection to domain seen in exploit attempts
  NOTICE([$note=Shellshock_Successful_Conn,
          $conn=c,
          $msg=fmt("Host %s connected to a domain seen in a shellshock exploit", c$id$orig_h),
          $sub=fmt("Domain: %s",value),
          $identifier=cat(c$id$orig_h,value)]);
}

event mime_one_header(c: connection, h: mime_header_rec)
{
if ( ! c?$smtp || ! h?$value ) return;
if (  shellshock_pattern !in h$value ) return;

NOTICE([$note=Shellshock_SMTP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock SMTP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",h$value),
        $identifier=cat(c$id$orig_h,c$id$resp_h,h$value)]);

if ( find_ip(h$value) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in h$value )
  find_domain(h$value);
}

# event to identify endpoints connecting to IP addresses seen in shellshock exploit attempts
event connection_state_remove(c: connection)
{
if ( c$id$resp_h in shellshock_servers )
  # generate a notice of connection to IP address seen in exploit attempts
  NOTICE([$note=Shellshock_Successful_Conn,
          $conn=c,
          $msg=fmt("Host %s connected to an IP address seen in a shellshock exploit", c$id$orig_h),
          $sub=fmt("IP address: %s",c$id$resp_h),
          $identifier=cat(c$id$orig_h,c$id$resp_h)]);
}

# Monitors traffic for Shellshock exploit attempts
# When exploits are seen containing IP addresses and domain values, monitors traffic for 60 minutes watching for endpoints to connect to IP addresses and domains seen in exploit attempts
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/frameworks/notice
@load base/protocols/http
@load base/protocols/dhcp

module CrowdStrike;

export {
  redef enum Notice::Type += {
    Shellshock_DHCP,
    Shellshock_HTTP,
    Shellshock_Successful_Conn
  };
}

const shellshock_pattern = /(\(|%28)(\)|%29)( |%20)(\{|%7B)/ &redef;

const shellshock_commands = /wget/ | /curl/;

global shellshock_servers: set[addr] &create_expire=60min &synchronized;
global shellshock_hosts: set[string] &create_expire=60min &synchronized;

# function to locate and extract domains seen in shellshock exploit attempts
function find_domain(ss: string)
{
local parts1 = split_all(ss,/[[:space:]]/);
for ( p in parts1 )
  if ( "http" in parts1[p] )
    {
    local parts2 = split_all(parts1[p],/\//);
    local output = parts2[5];
    }

if ( output !in shellshock_hosts )
  add shellshock_hosts[output];
}

# function to locate and extract IP addresses seen in shellshock exploit attempts
function find_ip(ss: string): bool
{
local b = F;
local remote_servers = find_ip_addresses(ss);

if ( |remote_servers| > 0 )
  {
  b = T;
  for ( rs in remote_servers )
    {
    local s = to_addr(remote_servers[rs]);
    if ( s !in shellshock_servers )
      add shellshock_servers[s];
    }
  }

return b;
}

# event to identify shellshock HTTP exploit attempts
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=3
{
if ( ! is_orig ) return;
if ( shellshock_pattern !in value ) return;

# generate a notice of the HTTP exploit attempt
NOTICE([$note=Shellshock_HTTP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock HTTP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",value),
        $identifier=cat(c$id$orig_h,c$id$resp_h,value)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(value) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in value )
  find_domain(value);
}

# event to identify shellshock DHCP exploit attempts
event dhcp_ack(c: connection, msg: dhcp_msg, mask: addr, router: dhcp_router_list, lease: interval, serv_addr: addr, host_name: string)
{
if ( shellshock_pattern !in host_name ) return;

# generate a notice of the DHCP exploit attempt
NOTICE([$note=Shellshock_DHCP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock DHCP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",host_name),
        $identifier=cat(c$id$orig_h,c$id$resp_h,host_name)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(host_name) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in host_name )
  find_domain(host_name);
}

# event to identify endpoints connecting to domains seen in shellshock exploit attempts
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=5
{
if ( name != "HOST" ) return;
if ( value in shellshock_hosts )
  # generate a notice of HTTP connection to domain seen in exploit attempts
  NOTICE([$note=Shellshock_Successful_Conn,
          $conn=c,
          $msg=fmt("Host %s connected to a domain seen in a shellshock exploit", c$id$orig_h),
          $sub=fmt("Domain: %s",value),
          $identifier=cat(c$id$orig_h,value)]);
}

# event to identify endpoints connecting to IP addresses seen in shellshock exploit attempts
event connection_state_remove(c: connection)
{
if ( c$id$resp_h in shellshock_servers )
  # generate a notice of connection to IP address seen in exploit attempts
  NOTICE([$note=Shellshock_Successful_Conn,
          $conn=c,
          $msg=fmt("Host %s connected to an IP address seen in a shellshock exploit", c$id$orig_h),
          $sub=fmt("IP address: %s",c$id$resp_h),
          $identifier=cat(c$id$orig_h,c$id$resp_h)]);
}


Update detect-ms15-034.bro

Update detect-ms15-034.bro

Added a new notice (MS15034_Server_Crash) and extra code to account for web server crashes due to the vulnerability. Thanks to @neu5ron for pointing this out.

# Detects MS15-034 vulnerabilities by inspecting inbound HTTP requests and web server responses
# Any generated notices should have their sub value inspected for the appropriate exploitable RANGE values
# CrowdStrike 2015
# josh.liburdi@crowdstrike.com

@load base/frameworks/notice
@load base/protocols/http

module CrowdStrike;

export {
        redef enum Notice::Type += {
                MS15034_Vulnerability,
                MS15034_Server_Crash
        };
}

# RANGE values seen in each connection are stored here
global ranges: table[string] of string;

# RANGE values are extracted from client HTTP headers if the web server has a private IP address or if the web server is within the local network
event http_header(c: connection, is_orig: bool, name: string, value: string)
{
if ( ! is_orig )
        return;

if ( Site::is_local_addr(c$id$resp_h) == T || Site::is_private_addr(c$id$resp_h) == T )
        if ( name == "RANGE" )
                ranges[c$uid] = value;
}

# If an HTTP reply is seen for connections in the ranges table, then the web server's response code is checked for a match with the vulnerability
# A notice is generated that contains the client RANGE request
# If the server response code isn't 416, then the traffic is ignored
event http_reply(c: connection, version: string, code: count, reason: string)
{
if ( c$uid in ranges )
        {
        if ( code == 416 )
                {
                local sub_msg = fmt("Range used: %s",ranges[c$uid]);
                NOTICE([$note=MS15034_Vulnerability,
                        $conn=c,
                        $msg=fmt("%s may be vulnerable to MS15-034",c$id$resp_h),
                        $sub=sub_msg,
                        $identifier=cat(c$id$resp_h,ranges[c$uid])]);

                delete ranges[c$uid];
                }

        else if ( code != 416 )
                delete ranges[c$uid];
        }
}

# The ranges table is cleaned up as connections are dropped from Bro
# If no server response was seen, then assume the web server crashed
event connection_state_remove(c: connection)
{
if ( c?$http )
        if ( c$uid in ranges )
                {
                if ( ! c$http?$status_code )
                        {
                        local sub_msg = fmt("Range used: %s",ranges[c$uid]);
                        NOTICE([$note=MS15034_Server_Crash,
                                $conn=c,
                                $msg=fmt("%s may have crashed due to MS15-034",c$id$resp_h),
                                $sub=sub_msg,
                                $identifier=cat(c$id$resp_h,ranges[c$uid])]);

                        delete ranges[c$uid];
                        }
                else
                        delete ranges[c$uid];
                }
}

# Detects MS15-034 vulnerabilities by inspecting inbound HTTP requests and web server responses
# Any generated notices should have their sub value inspected for the appropriate exploitable RANGE values
# CrowdStrike 2015
# josh.liburdi@crowdstrike.com

@load base/frameworks/notice
@load base/protocols/http

module CrowdStrike;

export {
        redef enum Notice::Type += {
                MS15034_Vulnerability
        };
}

# RANGE values seen in each connection are stored here
global ranges: table[string] of string;

# RANGE values are extracted from client HTTP headers if the web server has a private IP address or if the web server is within the local network
event http_header(c: connection, is_orig: bool, name: string, value: string)
{
if ( ! is_orig )
        return;

if ( Site::is_local_addr(c$id$resp_h) == T || Site::is_private_addr(c$id$resp_h) == T )
        if ( name == "RANGE" )
                ranges[c$uid] = value;
}

# If an HTTP reply is seen for connections in the ranges table, then the web server's response code is checked for a match with the vulnerability
# A notice is generated that contains the client RANGE request
event http_reply(c: connection, version: string, code: count, reason: string)
{
if ( c$uid in ranges )
        {
        if ( code == 416 )
                {
                local sub_msg = fmt("Range used: %s",ranges[c$uid]);
                NOTICE([$note=MS15034_Vulnerability,
                        $conn=c,
                        $msg=fmt("%s may be vulnerable to MS15-034",c$id$resp_h),
                        $sub=sub_msg,
                        $identifier=cat(c$id$resp_h,ranges[c$uid])]);
                }

        delete ranges[c$uid];
        }
}

# The ranges table is cleaned up as connections are dropped from Bro
event connection_state_remove(c: connection)
{
if ( c?$http )
        if ( c$uid in ranges )
                delete ranges[c$uid];
}


Added a fix for Google's GREASE values

Added a fix for Google's GREASE values

This update identifies and ignores values as defined in https://tools.ietf.org/html/draft-davidben-tls-grease-01 which is used by Google clients like Chrome. This will allow one to detect Google clients with exceptional accuracy. These lines can be commented out if one is looking for a more un-altered analysis of the Client Hello packet.

# This Bro script appends JA3 to ssl.log
# Version 1.3 (June 2017)
#
# Authors: John B. Althouse (jalthouse@salesforce.com) & Jeff Atkinson (jatkinson@salesforce.com)
#
# Copyright (c) 2017, salesforce.com, inc.
# All rights reserved.
# Licensed under the BSD 3-Clause license.
# For full license text, see LICENSE.txt file in the repo root  or https://opensource.org/licenses/BSD-3-Clause

module JA3;

export {
redef enum Log::ID += { LOG };
}

type TLSFPStorage: record {
       client_version:  count &default=0 &log;
       client_ciphers:  string &default="" &log;
       extensions:      string &default="" &log;
       e_curves:        string &default="" &log;
       ec_point_fmt:    string &default="" &log;
};

redef record connection += {
       tlsfp: TLSFPStorage &optional;
};

redef record SSL::Info += {
  ja3:            string &optional &log;
## LOG FIELD VALUES ##
#  ja3_version:  string &optional &log;
#  ja3_ciphers:  string &optional &log;
#  ja3_extensions: string &optional &log;
#  ja3_ec:         string &optional &log;
#  ja3_ec_fmt:     string &optional &log;
};

# Google. https://tools.ietf.org/html/draft-davidben-tls-grease-01
const grease: set[int] = {
    2570,
    6682,
    10794,
    14906,
    19018,
    23130,
    27242,
    31354,
    35466,
    39578,
    43690,
    47802,
    51914,
    56026,
    60138,
    64250
};
const sep = "-";
event bro_init() {
    Log::create_stream(JA3::LOG,[$columns=TLSFPStorage, $path="tlsfp"]);
}

event ssl_extension(c: connection, is_orig: bool, code: count, val: string)
{
if ( ! c?$tlsfp )
    c$tlsfp=TLSFPStorage();
    if ( is_orig = T ) {
        if ( code in grease ) {
            next;
        }
        if ( c$tlsfp$extensions == "" ) {
            c$tlsfp$extensions = cat(code);
        }
        else {
            c$tlsfp$extensions = string_cat(c$tlsfp$extensions, sep,cat(code));
        }
    }
}

event ssl_extension_ec_point_formats(c: connection, is_orig: bool, point_formats: index_vec)
{
if ( !c?$tlsfp )
    c$tlsfp=TLSFPStorage();
    if ( is_orig = T ) {
        for ( i in point_formats ) {
            if ( point_formats[i] in grease ) {
            next;
            }
            if ( c$tlsfp$ec_point_fmt == "" ) {
            c$tlsfp$ec_point_fmt += cat(point_formats[i]);
            }
            else {
            c$tlsfp$ec_point_fmt += string_cat(sep,cat(point_formats[i]));
            }
        }
    }
}

event ssl_extension_elliptic_curves(c: connection, is_orig: bool, curves: index_vec)
{
    if ( !c?$tlsfp )
    c$tlsfp=TLSFPStorage();
    if ( is_orig = T  ) {
        for ( i in curves ) {
            if ( curves[i] in grease ) {
            next;
            }
            if ( c$tlsfp$e_curves == "" ) {
                c$tlsfp$e_curves += cat(curves[i]);
            }
            else {
                c$tlsfp$e_curves += string_cat(sep,cat(curves[i]));
            }
        }
    }
}

event ssl_client_hello(c: connection, version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec) &priority=1
{
    if ( !c?$tlsfp )
    c$tlsfp=TLSFPStorage();
    c$tlsfp$client_version = version;
    for ( i in ciphers ) {
        if ( ciphers[i] in grease ) {
            next;
        }
        if ( c$tlsfp$client_ciphers == "" ) {
            c$tlsfp$client_ciphers += cat(ciphers[i]);
        }
        else {
            c$tlsfp$client_ciphers += string_cat(sep,cat(ciphers[i]));
        }
    }
    local sep2 = ",";
    local ja3_string = string_cat(cat(c$tlsfp$client_version),sep2,c$tlsfp$client_ciphers,sep2,c$tlsfp$extensions,sep2,c$tlsfp$e_curves,sep2,c$tlsfp$ec_point_fmt);
    local tlsfp_1 = md5_hash(ja3_string);
    c$ssl$ja3 = tlsfp_1;

## LOG FIELD VALUES ##
#c$ssl$ja3_version = cat(c$tlsfp$client_version);
#c$ssl$ja3_ciphers = c$tlsfp$client_ciphers;
#c$ssl$ja3_extensions = c$tlsfp$extensions;
#c$ssl$ja3_ec = c$tlsfp$e_curves;
#c$ssl$ja3_ec_fmt = c$tlsfp$ec_point_fmt;
#
## FOR DEBUGGING ##
#print "JA3: "+tlsfp_1+" Fingerprint String: "+ja3_string;

}

# This Bro script appends JA3 to ssl.log
# Version 1.2 (March 2017)
#
# Authors: John B. Althouse (jalthouse@salesforce.com) & Jeff Atkinson (jatkinson@salesforce.com)
#
# Copyright (c) 2017, salesforce.com, inc.
# All rights reserved.
# Licensed under the BSD 3-Clause license.
# For full license text, see LICENSE.txt file in the repo root  or https://opensource.org/licenses/BSD-3-Clause

module JA3;

export {
redef enum Log::ID += { LOG };
}

type TLSFPStorage: record {
       client_version:  count &default=0 &log;
       client_ciphers:  string &default="" &log;
       extensions:      string &default="" &log;
       e_curves:        string &default="" &log;
       ec_point_fmt:    string &default="" &log;
};

redef record connection += {
       tlsfp: TLSFPStorage &optional;
};

redef record SSL::Info += {
  ja3:            string &optional &log;
## LOG FIELD VALUES ##
#  ja3_version:  string &optional &log;
#  ja3_ciphers:  string &optional &log;
#  ja3_extensions: string &optional &log;
#  ja3_ec:         string &optional &log;
#  ja3_ec_fmt:     string &optional &log;
};

const sep = "-";
event bro_init() {
  Log::create_stream(JA3::LOG,[$columns=TLSFPStorage, $path="tlsfp"]);
}

event ssl_extension(c: connection, is_orig: bool, code: count, val: string)
{
if ( ! c?$tlsfp )
    c$tlsfp=TLSFPStorage();
    if ( is_orig = T ) {
        if ( c$tlsfp$extensions == "" ) {
            c$tlsfp$extensions = cat(code);
        }
        else {
            c$tlsfp$extensions = string_cat(c$tlsfp$extensions, sep,cat(code));
        }
    }
}

event ssl_extension_ec_point_formats(c: connection, is_orig: bool, point_formats: index_vec)
{
if ( !c?$tlsfp )
    c$tlsfp=TLSFPStorage();
    if ( is_orig = T ) {
        for ( i in point_formats ) {
        if ( c$tlsfp$ec_point_fmt == "" ) {
            c$tlsfp$ec_point_fmt += cat(point_formats[i]);
            }
      else {
            c$tlsfp$ec_point_fmt += string_cat(sep,cat(point_formats[i]));
            }
        }
    }
}

event ssl_extension_elliptic_curves(c: connection, is_orig: bool, curves: index_vec)
{
    if ( !c?$tlsfp )
    c$tlsfp=TLSFPStorage();
    if ( is_orig = T  ) {
        for ( i in curves ) {
            if ( c$tlsfp$e_curves == "" ) {
                c$tlsfp$e_curves += cat(curves[i]);
          }
            else {
                c$tlsfp$e_curves += string_cat(sep,cat(curves[i]));
          }
        }
    }
}

event ssl_client_hello(c: connection, version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec) &priority=1
{
    if ( !c?$tlsfp )
    c$tlsfp=TLSFPStorage();
    c$tlsfp$client_version = version;
    for ( i in ciphers ) {
        if ( c$tlsfp$client_ciphers == "" ) {
            c$tlsfp$client_ciphers += cat(ciphers[i]);
        }
        else {
            c$tlsfp$client_ciphers += string_cat(sep,cat(ciphers[i]));
      }
    }
    local sep2 = ",";
    local ja3_string = string_cat(cat(c$tlsfp$client_version),sep2,c$tlsfp$client_ciphers,sep2,c$tlsfp$extensions,sep2,c$tlsfp$e_curves,sep2,c$tlsfp$ec_point_fmt);
    local tlsfp_1 = md5_hash(ja3_string);
    c$ssl$ja3 = tlsfp_1;

## LOG FIELD VALUES ##
#c$ssl$ja3_version = cat(c$tlsfp$client_version);
#c$ssl$ja3_ciphers = c$tlsfp$client_ciphers;
#c$ssl$ja3_extensions = c$tlsfp$extensions;
#c$ssl$ja3_ec = c$tlsfp$e_curves;
#c$ssl$ja3_ec_fmt = c$tlsfp$ec_point_fmt;
#
## FOR DEBUGGING ##
#print "JA3: "+tlsfp_1+" Fingerprint String: "+ja3_string;

}


Add redef statement

Add redef statement

Added a redef statement for torlist_location.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/unusedPhD/CrowdStrike_cs-bro.git/'


Create __load__.bro

Create __load__.bro

error: pathspec 'bro-scripts/msb/__load__.bro' did not match any file(s) known to git


bro-scripts: ipmi.bro: added new script that watches for IPMI traffic

# Written by Jon Schipp
# Rudimentary detection of IPMI traffic by matching destination port 623/udp
# 	To use:
# 		1. Add script to configuration local.bro: $ echo '@load ipmi.bro' >> $BROPREFIX/share/bro/site/local.bro
# 		2. Copy script to $BROPREFIX/share/bro/site
# 		3. $ broctl check && broctl install && broctl restart

@load base/frameworks/notice

export {
        redef enum Notice::Type += {
                IPMI::Port_Detected
        };
}

redef Notice::emailed_types += {
        IPMI::Port_Detected
};

event new_connection(c: connection)
        {
        if ( ! Site::is_local_addr(c$id$orig_h) && Site::is_local_addr(c$id$resp_h) && c$id$resp_p == 623/udp)
                {
                NOTICE([$note=IPMI::Port_Detected, $msg=fmt("Host %s sent traffic to UDP port %d.", c$id$orig_h, c$id$resp_p),
		$conn=c, $identifier=cat(c$id$orig_h,c$id$resp_p), $suppress_for=1day]);
                }
        }

error: pathspec 'ipmi.bro' did not match any file(s) known to git


Fixed for any version of Bro 2.x

Fixed for any version of Bro 2.x

As identified by @theflakes, this script had some issues if you ran it on Bro 2.4. This change makes the script simpler and should make it compatible with any version of Bro 2.x.

# Support for UDP, ICMP, and non-established TCP connections
# This will only generate Intel matches when a connection is removed from Bro
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/frameworks/intel
@load policy/frameworks/intel/seen/where-locations

event connection_state_remove(c: connection)
{
if ( c$conn?$proto && ( c$conn$proto != tcp || ( c$conn?$history && c$conn$proto == tcp && "h" !in c$conn$history ) ) )
        {
        Intel::seen([$host=c$id$orig_h, $conn=c, $where=Conn::IN_ORIG]);
        Intel::seen([$host=c$id$resp_h, $conn=c, $where=Conn::IN_RESP]);
        }
}

# Support for UDP, ICMP, and non-established TCP connections
# This will only generate Intel matches when a connection is removed from Bro
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/frameworks/intel
@load policy/frameworks/intel/seen/where-locations

event Conn::log_conn(rec: Conn::Info)
{
if ( rec?$proto && ( rec$proto != tcp || ( rec?$history && rec$proto == tcp && "h" !in rec$history ) ) )
  {
  # duration, start_time, addl, and hot are required fields although they are not used by Intel framework
  local dur: interval;
  local history: string;

  if ( rec?$duration )
    dur = rec$duration;
  else dur = 0secs;

  if ( rec?$history )
    history = rec$history;
  else history = "";

  local c = [$uid = rec$uid,$id = rec$id,$history = history,$duration = dur,$start_time = 0,$addl = "",$hot = 0];

  Intel::seen([$host=c$id$orig_h, $conn=c, $where=Conn::IN_ORIG]);
  Intel::seen([$host=c$id$resp_h, $conn=c, $where=Conn::IN_RESP]);
  }
}


ntp-audit.bro: add ability to exclude subnets

# Written by Jon Schipp, 01-10-2013
#
# Detects when hosts send NTP messages to NTP servers not defined in time_servers.
#       To use:
#               1. Enable the NTP analyzer:
#
#               If running Bro 2.1 add lines to local.bro:
#
#                       global ports = set(123/udp);
#                       redef dpd_config += { [ANALYZER_NTP] = [$ports = ports]
#                       };
#
#               If running Bro 2.2 add lines to local.bro:
#
#                       event bro_init()
#                       {
#                              local ports = set(123/udp);
#                              Analyzer::register_for_ports(Analyzer::ANALYZER_NTP,
#                              ports);
#                       }
#
#               2. Copy ntp-audit.bro script to $BROPREFIX/share/bro/site
#               3. Place the following line into local.bro and put above code from step 1:
#                       @load ntp-audit.bro
#               4. Run commands to validate the script, install it, and put into production:
#                       $ broctl check && broctl install && broctl restart
#
#       If you would like to receive e-mails when a notice event is generated add to emailed_types in local.bro:
#               e.g.
#                       redef Notice::emailed_types += {
#                               MyScripts::Query_Sent_To_Wrong_Server,
#                       };

@load base/frameworks/notice

# Use namespace so variables don't conflict with those in other scripts
module MyScripts;

# Export sets and types so that they can be redefined outside of this script
export {

        redef enum Notice::Type += {
                Query_Sent_To_Wrong_Server
        };

        # List your NTP servers here
        const time_servers: set[addr] = {
        192.168.1.250,
        192.168.1.251,
        } &redef;

	const subnet_exclude: set[subnet] = {
        192.168.2.0/24, # Exclude mobile network
        } &redef;

        # List any source addresses that should be excluded
        const time_exclude: set[addr] = {
        192.168.1.250,
        192.168.1.251,
        192.168.1.1, # Gateway/NAT/WAN uses external source for time
        } &redef;

}

event ntp_message(u: connection, msg: ntp_msg, excess: string)
        {

	 # Exit event handler if originator is not in networks.cfg
	if (! Site::is_local_addr(u$id$orig_h) )
		return;

	if ( u$id$orig_h in subnet_exclude )
                return;

        if ( u$id$orig_h !in time_exclude && u$id$resp_h !in time_servers )
                {
                NOTICE([$note=Query_Sent_To_Wrong_Server,
                $msg="NTP query destined to non-defined NTP servers", $conn=u,
                $identifier=cat(u$id$orig_h),
                $suppress_for=1day]);
                }
        }
~

# Written by Jon Schipp, 01-10-2013
#
# Detects when hosts send NTP messages to NTP servers not defined in time_servers.
#       To use:
#               1. Enable the NTP analyzer:
#
#               If running Bro 2.1 add lines to local.bro:
#
#                       global ports = set(123/udp);
#                       redef dpd_config += { [ANALYZER_NTP] = [$ports = ports]
#                       };
#
#               If running Bro 2.2 add lines to local.bro:
#
#                       event bro_init()
#                       {
#                              local ports = set(123/udp);
#                              Analyzer::register_for_ports(Analyzer::ANALYZER_NTP,
#                              ports);
#                       }
#
#               2. Copy ntp-audit.bro script to $BROPREFIX/share/bro/site
#               3. Place the following line into local.bro and put above code from step 1:
#                       @load ntp-audit.bro
#               4. Run commands to validate the script, install it, and put into production:
#                       $ broctl check && broctl install && broctl restart
#
#       If you would like to receive e-mails when a notice event is generated add to emailed_types in local.bro:
#               e.g.
#                       redef Notice::emailed_types += {
#                               MyScripts::Query_Sent_To_Wrong_Server,
#                       };

@load base/frameworks/notice

# Use namespace so variables don't conflict with those in other scripts
module MyScripts;

# Export sets and types so that they can be redefined outside of this script
export {

        redef enum Notice::Type += {
                Query_Sent_To_Wrong_Server
        };

        # List your NTP servers here
        const time_servers: set[addr] = {
        192.168.1.250,
        192.168.1.251,
        } &redef;

        # List any source addresses that should be excluded
        const time_exclude: set[addr] = {
        192.168.1.250,
        192.168.1.251,
        192.168.1.1, # Gateway/NAT/WAN uses external source for time
        } &redef;

}

event ntp_message(u: connection, msg: ntp_msg, excess: string)
        {

	 # Exit event handler if originator is not in networks.cfg
	if (! Site::is_local_addr(u$id$orig_h) )
		return;

        if ( u$id$orig_h !in time_exclude && u$id$resp_h !in time_servers )
                {
                NOTICE([$note=Query_Sent_To_Wrong_Server,
                $msg="NTP query destined to non-defined NTP servers", $conn=u,
                $identifier=cat(u$id$orig_h),
                $suppress_for=1day]);
                }
        }
~


Delete detect-rfd.bro

Delete detect-rfd.bro

# Detect reflected file download attacks as described in
# http://packetstorm.foofus.com/papers/presentations/eu-14-Hafif-Reflected-File-Download-A-New-Web-Attack-Vector.pdf
# CrowdStrike 2015
# josh.liburdi@crowdstrike.com

@load base/frameworks/notice
@load base/protocols/http

module CrowdStrike;

export {
  redef enum Notice::Type += {
    Reflected_File_Download
  };

  redef enum HTTP::Tags += {
    RFD
  };
}

const rfd_content_type: set[string] = {
  "application/json",
  "application/x-javascript",
  "application/javascript",
  "application/notexist",
  "text/json",
  "text/x-javascript",
  "text/plain",
  "text/notexist",
  "application/xml",
  "text/xml",
  "text/html"
} &redef;

const rfd_pattern =   /\"\|\|/ |
                      /\"\<\</ |
                      /\"\>\>/ |
                      /[^?]*\.bat(\;|$)/ |
                      /[^?]*\.cmd(\;|$)/ |
                      /[:alnum:]*?[Ss][Ee][Tt][Uu][Pp][:alnum:]*?\.[:alpha:]{3,4}(\;|$)/ |
                      /[:alnum:]*?[Ii][nn][Ss][Tt][Aa][Ll][Ll][:alnum:]*?\.[:alpha:]{3,4}(\;|$)/ |
                      /[:alnum:]*?[Uu][Pp][Dd][Aa][Tt][Ee][:alnum:]*?\.[:alpha:]{3,4}(\;|$)/ |
                      /[:alnum:]*?[Uu][Nn][In][Ss][Tt][:alnum:]*?\.[:alpha:]{3,4}(\;|$)/ &redef;

# Perform a pattern match for reflected file downloads.
# If found, add HTTP tag and generate a notice.
function rfd_do_notice(c: connection)
{
if ( rfd_pattern in c$http$uri )
  {
  add c$http$tags[RFD];
  NOTICE([$note=Reflected_File_Download,
          $msg="A reflected file download was attempted",
          $sub=c$http$uri,
          $id=c$id,
          $identifier=cat(c$id$orig_h,c$id$resp_h,c$http$uri)]);
  }
}

# Check for reflected file downloads in HTTP transactions that have a
# CONTENT-TYPE header. Valid RFD content types are identified anywhere
# in the CONTENT-TYPE header value.
event http_header(c: connection, is_orig: bool, name: string, value: string)
{
if ( is_orig || ! c$http?$uri || c$http$uri == "" ) return;

if ( name == "CONTENT-TYPE" )
  for ( rct in rfd_content_type )
    if ( rct in value )
      rfd_do_notice(c);
}

# Check for reflected file downloads in HTTP transactions that do not have a
# CONTENT-TYPE header.
event http_all_headers(c: connection, is_orig: bool, hlist: mime_header_list)
{
if ( is_orig || ! c$http?$uri || c$http$uri == "" ) return;

local headers: set[string];

for ( h in hlist )
  {
  local name = hlist[h]$name;
  add headers[name];
  }

if ( "CONTENT-TYPE" !in headers )
  rfd_do_notice(c);
else return;
}


Create detect-rfd.bro

Create detect-rfd.bro

# Detect reflected file download attacks as described in
# http://packetstorm.foofus.com/papers/presentations/eu-14-Hafif-Reflected-File-Download-A-New-Web-Attack-Vector.pdf
# CrowdStrike 2015
# josh.liburdi@crowdstrike.com

@load base/frameworks/notice
@load base/protocols/http

module CrowdStrike;

export {
  redef enum Notice::Type += {
    Reflected_File_Download
  };

  redef enum HTTP::Tags += {
    RFD
  };
}

const rfd_content_type: set[string] = {
  "application/json",
  "application/x-javascript",
  "application/javascript",
  "application/notexist",
  "text/json",
  "text/x-javascript",
  "text/plain",
  "text/notexist",
  "application/xml",
  "text/xml",
  "text/html"
};

const rfd_pattern = 	/\"\|\|/ |
                			/\"\<\</ |
                			/\"\>\>/ |
                			/\;\/([:alnum:]|[:punct])*\.bat\;/ |
                			/\;\/([:alnum:]|[:punct])*\.cmd\;/ |
                			/\;\/[:alnum:]*?[Ss][Ee][Tt][Uu][Pp][:alnum:]*?\.[:alpha:]{3,4}\;/ |
                			/\;\/[:alnum:]*?[Ii][nn][Ss][Tt][Aa][Ll][Ll][:alnum:]*?\.[:alpha:]{3,4}\;/ |
                			/\;\/[:alnum:]*?[Uu][Pp][Dd][Aa][Tt][Ee][:alnum:]*?\.[:alpha:]{3,4}\;/ |
                			/\;\/[:alnum:]*?[Uu][Nn][In][Ss][Tt][:alnum:]*?\.[:alpha:]{3,4}\;/;

# Perform a pattern match for reflected file downloads.
# If found, add HTTP tag and generate a notice.
function rfd_do_notice(c: connection)
{
if ( rfd_pattern in c$http$uri )
  {
  add c$http$tags[RFD];
  NOTICE([$note=Reflected_File_Download,
          $msg="A reflected file download was attempted",
          $sub=c$http$uri,
          $id=c$id,
          $identifier=cat(c$id$orig_h,c$id$resp_h,c$http$uri)]);
  }
}

# Check for reflected file downloads in HTTP transactions that have a
# CONTENT-TYPE header. Valid RFD content types are identified anywhere
# in the CONTENT-TYPE header value.
event http_header(c: connection, is_orig: bool, name: string, value: string)
{
if ( is_orig || ! c$http?$uri || c$http$uri == "" ) return;

if ( name == "CONTENT-TYPE" )
  for ( rct in rfd_content_type )
    if ( rct in value )
      rfd_do_notice(c);
}

# Check for reflected file downloads in HTTP transactions that do not have a
# CONTENT-TYPE header.
event http_all_headers(c: connection, is_orig: bool, hlist: mime_header_list)
{
if ( is_orig || ! c$http?$uri || c$http$uri == "" ) return;

local headers: set[string];

for ( h in hlist )
  {
  local name = hlist[h]$name;
  add headers[name];
  }

if ( "CONTENT-TYPE" !in headers )
  rfd_do_notice(c);
else return;
}

error: pathspec 'bro-scripts/rfd/detect-rfd.bro' did not match any file(s) known to git


Create intel_ja3.bro

Create intel_ja3.bro

# This Bro script adds JA3 to the Bro Intel Framework as Intel::JA3
#
# Author: John B. Althouse (jalthouse@salesforce.com)
#
# Copyright (c) 2017, salesforce.com, inc.
# All rights reserved.
# Licensed under the BSD 3-Clause license.
# For full license text, see LICENSE.txt file in the repo root  or https://opensource.org/licenses/BSD-3-Clause

module Intel;

export {
    redef enum Intel::Type += { Intel::JA3 };
}

export {
    redef enum Intel::Where += { SSL::IN_JA3 };
}

event ssl_server_hello (c: connection, version: count, possible_ts: time, server_random: string, session_id: string, cipher: count, comp_method: count)
	{
	Intel::seen([$indicator=c$ssl$ja3, $indicator_type=Intel::JA3, $conn=c, $where=SSL::IN_JA3]);
	}

# This Bro script adds JA3 to the Bro Intel Framework as Intel::JA3
#
# Copyright (c) 2017, salesforce.com, inc.
# All rights reserved.
# Licensed under the BSD 3-Clause license.
# For full license text, see LICENSE.txt file in the repo root  or https://opensource.org/licenses/BSD-3-Clause

module Intel;

export {
    redef enum Intel::Type += { Intel::JA3 };
}

export {
    redef enum Intel::Where += { SSL::IN_JA3 };
}

event ssl_server_hello (c: connection, version: count, possible_ts: time, server_random: string, session_id: string, cipher: count, comp_method: count)
	{
	Intel::seen([$indicator=c$ssl$ja3, $indicator_type=Intel::JA3, $conn=c, $where=SSL::IN_JA3]);
	}


Create radius-username.bro

Create radius-username.bro

# Intel framework support for RADIUS usernames
# CrowdStrike 2014
# josh.liburdi@crowdstrike.com

@load base/protocols/radius
@load base/frameworks/intel
@load policy/frameworks/intel/seen/where-locations

event RADIUS::log_radius(rec: RADIUS::Info)
{
if ( rec?$username && rec?$result )
  if ( rec$result == "success" )
    Intel::seen([$indicator=rec$username,
                 $indicator_type=Intel::USER_NAME,
                 $where=RADIUS::IN_USER_NAME]);
}

error: pathspec 'bro-scripts/intel-extensions/seen/radius-username.bro' did not match any file(s) known to git


bro-scripts: capinfos.bro: added draft of a capinfos like bro script

# Written by Jon Schipp
# 	To use:
#		$ bro -b -r capture.pcap conn-count.bro

global connections_seen_counter = 0;
global orig_seen_s: set[addr];
global resp_seen_s: set[addr];
global time_first: time;
global time_last: time;

event bro_init()
	{
	print fmt("Bro Version: %s", bro_version());
	}

event new_connection(c: connection)
        {

	if ( connections_seen_counter == 0 )
		time_first = network_time();

	time_last = network_time();
	++connections_seen_counter;
	add orig_seen_s[c$id$orig_h];
	add resp_seen_s[c$id$resp_h];
        }

event bro_done()
	{
	print fmt("Start: %DT", time_first);
	print fmt("End: %DT", time_last);
	print fmt(" -- Connections Seen: --");
	print fmt("Connections: %d", connections_seen_counter);
	print fmt("Unique Originators: %d", |orig_seen_s|);
	print fmt("Unique Responders: %d", |resp_seen_s|);
	print fmt("Total Unique Hosts: %d", |orig_seen_s| + |resp_seen_s|);
	}

error: pathspec 'capinfos.bro' did not match any file(s) known to git


bro-scripts: ntp-audit.bro: added new Bro script

# Written by Jon Schipp
# Detects when hosts send NTP queries to NTP servers not defined in time_servers.
# 	To use:
# 		1. Add script to configuration local.bro: $ echo '@load ntp-audit.bro' >> $BROPREFIX/share/bro/site/local.bro
# 		2. Copy script to $BROPREFIX/share/bro/site
# 		3. $ broctl check && broctl install && broctl restart

@load base/frameworks/notice

# List your NTP servers here
const time_servers: set[addr] = {
	192.168.1.250,
	192.168.1.251,
} &redef;

# List any source addresses that should be excluded
const time_exclude: set[addr] = {
	192.168.1.250,
	192.168.1.251,
	192.168.1.1, # Gateway/NAT
} &redef;

export {

        redef enum Notice::Type += {
                NTP::Query_Sent_To_Wrong_Server
        };
}

redef Notice::emailed_types += {
                NTP::Query_Sent_To_Wrong_Server
};

event udp_request(u: connection)
        {
        if ( u$id$orig_h !in time_exclude && u$id$resp_h !in time_servers && u$id$resp_p == 123/udp )
                {
                NOTICE([$note=NTP::Query_Sent_To_Wrong_Server,
                $msg="NTP query destined to non-TOC NTP servers", $conn=u,
                $identifier=cat(u$id$orig_h),
                $suppress_for=1day]);
                }
        }

error: pathspec 'ntp-audit.bro' did not match any file(s) known to git


Create detect-shellshock.bro

Create detect-shellshock.bro

# Monitors traffic for Shellshock exploit attempts
# When exploits are seen containing IP addresses and domain values, monitors traffic for 60 minutes watching for endpoints to connect to IP addresses and domains seen in exploit attempts
# CrowdStrike 2014

@load base/frameworks/notice
@load base/protocols/http
@load base/protocols/dhcp

module CrowdStrike;

export {
  redef enum Notice::Type += {
    Shellshock_DHCP,
    Shellshock_HTTP,
    Shellshock_Successful_Conn
  };
}

const shellshock_pattern = /(\(|%28)(\)|%29)( |%20)(\{|%7B)/ &redef;

const shellshock_commands = /wget/ | /curl/;

global shellshock_servers: set[addr] &create_expire=60min &synchronized;
global shellshock_hosts: set[string] &create_expire=60min &synchronized;

# function to locate and extract domains seen in shellshock exploit attempts
function find_domain(ss: string)
{
local parts1 = split_all(ss,/[[:space:]]/);
for ( p in parts1 )
  if ( "http" in parts1[p] )
    {
    local parts2 = split_all(parts1[p],/\//);
    local output = parts2[5];
    }

if ( output !in shellshock_hosts )
  add shellshock_hosts[output];
}

# function to locate and extract IP addresses seen in shellshock exploit attempts
function find_ip(ss: string): bool
{
local b = F;
local remote_servers = find_ip_addresses(ss);

if ( |remote_servers| > 0 )
  {
  b = T;
  for ( rs in remote_servers )
    {
    local s = to_addr(remote_servers[rs]);
    if ( s !in shellshock_servers )
      add shellshock_servers[s];
    }
  }

return b;
}

# event to identify shellshock HTTP exploit attempts
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=3
{
if ( ! is_orig ) return;
if ( shellshock_pattern !in value ) return;

# generate a notice of the HTTP exploit attempt
NOTICE([$note=Shellshock_HTTP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock HTTP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",value),
        $identifier=cat(c$id$orig_h,c$id$resp_h,value)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(value) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in value )
  find_domain(value);
}

# event to identify shellshock DHCP exploit attempts
event dhcp_ack(c: connection, msg: dhcp_msg, mask: addr, router: dhcp_router_list, lease: interval, serv_addr: addr, host_name: string)
{
if ( shellshock_pattern !in host_name ) return;

# generate a notice of the DHCP exploit attempt
NOTICE([$note=Shellshock_DHCP,
        $conn=c,
        $msg=fmt("Host %s may have attempted a shellshock DHCP exploit against %s", c$id$orig_h, c$id$resp_h),
        $sub=fmt("Command: %s",host_name),
        $identifier=cat(c$id$orig_h,c$id$resp_h,host_name)]);

# check the exploit attempt for IP addresses
# if an IP address is found, then do not look for domains
if ( find_ip(host_name) == T ) return;

# check the exploit attempt for domains
if ( shellshock_commands in host_name )
  find_domain(host_name);
}

# event to identify endpoints connecting to domains seen in shellshock exploit attempts
event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=5
{
if ( name != "HOST" ) return;
if ( value in shellshock_hosts )
  # generate a notice of HTTP connection to domain seen in exploit attempts
  NOTICE([$note=Shellshock_Successful_Conn,
          $conn=c,
          $msg=fmt("Host %s connected to a domain seen in a shellshock exploit", c$id$orig_h),
          $sub=fmt("Domain: %s",value),
          $identifier=cat(c$id$orig_h,value)]);
}

# event to identify endpoints connecting to IP addresses seen in shellshock exploit attempts
event connection_state_remove(c: connection)
{
if ( c$id$resp_h in shellshock_servers )
  # generate a notice of connection to IP address seen in exploit attempts
  NOTICE([$note=Shellshock_Successful_Conn,
          $conn=c,
          $msg=fmt("Host %s connected to an IP address seen in a shellshock exploit", c$id$orig_h),
          $sub=fmt("IP address: %s",c$id$resp_h),
          $identifier=cat(c$id$orig_h,c$id$resp_h)]);
}

error: pathspec 'bro-scripts/detect-shellshock.bro' did not match any file(s) known to git