mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
Merge remote-tracking branch 'origin/master' into topic/johanna/tls-more-data
This commit is contained in:
commit
b1dbd757a6
1468 changed files with 41493 additions and 19065 deletions
|
@ -4,6 +4,7 @@
|
|||
##! use on a network per day.
|
||||
|
||||
@load base/utils/directions-and-hosts
|
||||
@load base/frameworks/cluster
|
||||
|
||||
module Known;
|
||||
|
||||
|
@ -19,23 +20,131 @@ export {
|
|||
## TCP connection.
|
||||
host: addr &log;
|
||||
};
|
||||
|
||||
## Toggles between different implementations of this script.
|
||||
## When true, use a Broker data store, else use a regular Bro set
|
||||
## with keys uniformly distributed over proxy nodes in cluster
|
||||
## operation.
|
||||
const use_host_store = T &redef;
|
||||
|
||||
## The hosts whose existence should be logged and tracked.
|
||||
## See :bro:type:`Host` for possible choices.
|
||||
const host_tracking = LOCAL_HOSTS &redef;
|
||||
|
||||
## Holds the set of all known hosts. Keys in the store are addresses
|
||||
## and their associated value will always be the "true" boolean.
|
||||
global host_store: Cluster::StoreInfo;
|
||||
|
||||
## The Broker topic name to use for :bro:see:`Known::host_store`.
|
||||
const host_store_name = "bro/known/hosts" &redef;
|
||||
|
||||
## The expiry interval of new entries in :bro:see:`Known::host_store`.
|
||||
## This also changes the interval at which hosts get logged.
|
||||
const host_store_expiry = 1day &redef;
|
||||
|
||||
## The timeout interval to use for operations against
|
||||
## :bro:see:`Known::host_store`.
|
||||
const host_store_timeout = 15sec &redef;
|
||||
|
||||
## The set of all known addresses to store for preventing duplicate
|
||||
## logging of addresses. It can also be used from other scripts to
|
||||
## inspect if an address has been seen in use.
|
||||
## Maintain the list of known hosts for 24 hours so that the existence
|
||||
## of each individual address is logged each day.
|
||||
global known_hosts: set[addr] &create_expire=1day &synchronized &redef;
|
||||
##
|
||||
## In cluster operation, this set is distributed uniformly across
|
||||
## proxy nodes.
|
||||
global hosts: set[addr] &create_expire=1day &redef;
|
||||
|
||||
## An event that can be handled to access the :bro:type:`Known::HostsInfo`
|
||||
## record as it is sent on to the logging framework.
|
||||
global log_known_hosts: event(rec: HostsInfo);
|
||||
}
|
||||
|
||||
event bro_init()
|
||||
{
|
||||
if ( ! Known::use_host_store )
|
||||
return;
|
||||
|
||||
Known::host_store = Cluster::create_store(Known::host_store_name);
|
||||
}
|
||||
|
||||
event Known::host_found(info: HostsInfo)
|
||||
{
|
||||
if ( ! Known::use_host_store )
|
||||
return;
|
||||
|
||||
when ( local r = Broker::put_unique(Known::host_store$store, info$host,
|
||||
T, Known::host_store_expiry) )
|
||||
{
|
||||
if ( r$status == Broker::SUCCESS )
|
||||
{
|
||||
if ( r$result as bool )
|
||||
Log::write(Known::HOSTS_LOG, info);
|
||||
}
|
||||
else
|
||||
Reporter::error(fmt("%s: data store put_unique failure",
|
||||
Known::host_store_name));
|
||||
}
|
||||
timeout Known::host_store_timeout
|
||||
{
|
||||
# Can't really tell if master store ended up inserting a key.
|
||||
Log::write(Known::HOSTS_LOG, info);
|
||||
}
|
||||
}
|
||||
|
||||
event known_host_add(info: HostsInfo)
|
||||
{
|
||||
if ( use_host_store )
|
||||
return;
|
||||
|
||||
if ( info$host in Known::hosts )
|
||||
return;
|
||||
|
||||
add Known::hosts[info$host];
|
||||
|
||||
@if ( ! Cluster::is_enabled() ||
|
||||
Cluster::local_node_type() == Cluster::PROXY )
|
||||
Log::write(Known::HOSTS_LOG, info);
|
||||
@endif
|
||||
}
|
||||
|
||||
event Cluster::node_up(name: string, id: string)
|
||||
{
|
||||
if ( use_host_store )
|
||||
return;
|
||||
|
||||
if ( Cluster::local_node_type() != Cluster::WORKER )
|
||||
return;
|
||||
|
||||
# Drop local suppression cache on workers to force HRW key repartitioning.
|
||||
Known::hosts = set();
|
||||
}
|
||||
|
||||
event Cluster::node_down(name: string, id: string)
|
||||
{
|
||||
if ( use_host_store )
|
||||
return;
|
||||
|
||||
if ( Cluster::local_node_type() != Cluster::WORKER )
|
||||
return;
|
||||
|
||||
# Drop local suppression cache on workers to force HRW key repartitioning.
|
||||
Known::hosts = set();
|
||||
}
|
||||
|
||||
event Known::host_found(info: HostsInfo)
|
||||
{
|
||||
if ( use_host_store )
|
||||
return;
|
||||
|
||||
if ( info$host in Known::hosts )
|
||||
return;
|
||||
|
||||
Cluster::publish_hrw(Cluster::proxy_pool, info$host, known_host_add, info);
|
||||
event known_host_add(info);
|
||||
}
|
||||
|
||||
event bro_init()
|
||||
{
|
||||
Log::create_stream(Known::HOSTS_LOG, [$columns=HostsInfo, $ev=log_known_hosts, $path="known_hosts"]);
|
||||
|
@ -43,17 +152,15 @@ event bro_init()
|
|||
|
||||
event connection_established(c: connection) &priority=5
|
||||
{
|
||||
if ( c$orig$state != TCP_ESTABLISHED )
|
||||
return;
|
||||
|
||||
if ( c$resp$state != TCP_ESTABLISHED )
|
||||
return;
|
||||
|
||||
local id = c$id;
|
||||
|
||||
|
||||
for ( host in set(id$orig_h, id$resp_h) )
|
||||
{
|
||||
if ( host !in known_hosts &&
|
||||
c$orig$state == TCP_ESTABLISHED &&
|
||||
c$resp$state == TCP_ESTABLISHED &&
|
||||
addr_matches_host(host, host_tracking) )
|
||||
{
|
||||
add known_hosts[host];
|
||||
Log::write(Known::HOSTS_LOG, [$ts=network_time(), $host=host]);
|
||||
}
|
||||
}
|
||||
if ( addr_matches_host(host, host_tracking) )
|
||||
event Known::host_found([$ts = network_time(), $host = host]);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
##! during the session, the protocol will also be logged.
|
||||
|
||||
@load base/utils/directions-and-hosts
|
||||
@load base/frameworks/cluster
|
||||
|
||||
module Known;
|
||||
|
||||
|
@ -25,15 +26,47 @@ export {
|
|||
## A set of protocols that match the service's connection payloads.
|
||||
service: set[string] &log;
|
||||
};
|
||||
|
||||
## Toggles between different implementations of this script.
|
||||
## When true, use a Broker data store, else use a regular Bro set
|
||||
## with keys uniformly distributed over proxy nodes in cluster
|
||||
## operation.
|
||||
const use_service_store = T &redef;
|
||||
|
||||
## The hosts whose services should be tracked and logged.
|
||||
## See :bro:type:`Host` for possible choices.
|
||||
const service_tracking = LOCAL_HOSTS &redef;
|
||||
|
||||
type AddrPortPair: record {
|
||||
host: addr;
|
||||
p: port;
|
||||
};
|
||||
|
||||
## Holds the set of all known services. Keys in the store are
|
||||
## :bro:type:`Known::AddrPortPair` and their associated value is
|
||||
## always the boolean value of "true".
|
||||
global service_store: Cluster::StoreInfo;
|
||||
|
||||
## The Broker topic name to use for :bro:see:`Known::service_store`.
|
||||
const service_store_name = "bro/known/services" &redef;
|
||||
|
||||
## The expiry interval of new entries in :bro:see:`Known::service_store`.
|
||||
## This also changes the interval at which services get logged.
|
||||
const service_store_expiry = 1day &redef;
|
||||
|
||||
## The timeout interval to use for operations against
|
||||
## :bro:see:`Known::service_store`.
|
||||
const service_store_timeout = 15sec &redef;
|
||||
|
||||
## Tracks the set of daily-detected services for preventing the logging
|
||||
## of duplicates, but can also be inspected by other scripts for
|
||||
## different purposes.
|
||||
global known_services: set[addr, port] &create_expire=1day &synchronized;
|
||||
##
|
||||
## In cluster operation, this set is uniformly distributed across
|
||||
## proxy nodes.
|
||||
##
|
||||
## This set is automatically populated and shouldn't be directly modified.
|
||||
global services: set[addr, port] &create_expire=1day;
|
||||
|
||||
## Event that can be handled to access the :bro:type:`Known::ServicesInfo`
|
||||
## record as it is sent on to the logging framework.
|
||||
|
@ -46,46 +79,124 @@ redef record connection += {
|
|||
known_services_done: bool &default=F;
|
||||
};
|
||||
|
||||
event bro_init() &priority=5
|
||||
|
||||
event bro_init()
|
||||
{
|
||||
Log::create_stream(Known::SERVICES_LOG, [$columns=ServicesInfo,
|
||||
$ev=log_known_services,
|
||||
$path="known_services"]);
|
||||
if ( ! Known::use_service_store )
|
||||
return;
|
||||
|
||||
Known::service_store = Cluster::create_store(Known::service_store_name);
|
||||
}
|
||||
|
||||
event log_it(ts: time, a: addr, p: port, services: set[string])
|
||||
|
||||
event service_info_commit(info: ServicesInfo)
|
||||
|
||||
{
|
||||
if ( [a, p] !in known_services )
|
||||
if ( ! Known::use_service_store )
|
||||
return;
|
||||
|
||||
local key = AddrPortPair($host = info$host, $p = info$port_num);
|
||||
|
||||
when ( local r = Broker::put_unique(Known::service_store$store, key,
|
||||
T, Known::service_store_expiry) )
|
||||
{
|
||||
add known_services[a, p];
|
||||
|
||||
local i: ServicesInfo;
|
||||
i$ts=ts;
|
||||
i$host=a;
|
||||
i$port_num=p;
|
||||
i$port_proto=get_port_transport_proto(p);
|
||||
i$service=services;
|
||||
Log::write(Known::SERVICES_LOG, i);
|
||||
if ( r$status == Broker::SUCCESS )
|
||||
{
|
||||
if ( r$result as bool )
|
||||
Log::write(Known::SERVICES_LOG, info);
|
||||
}
|
||||
else
|
||||
Reporter::error(fmt("%s: data store put_unique failure",
|
||||
Known::service_store_name));
|
||||
}
|
||||
timeout Known::service_store_timeout
|
||||
{
|
||||
Log::write(Known::SERVICES_LOG, info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
event known_service_add(info: ServicesInfo)
|
||||
{
|
||||
if ( Known::use_service_store )
|
||||
return;
|
||||
|
||||
if ( [info$host, info$port_num] in Known::services )
|
||||
return;
|
||||
|
||||
add Known::services[info$host, info$port_num];
|
||||
|
||||
@if ( ! Cluster::is_enabled() ||
|
||||
Cluster::local_node_type() == Cluster::PROXY )
|
||||
Log::write(Known::SERVICES_LOG, info);
|
||||
@endif
|
||||
}
|
||||
|
||||
event Cluster::node_up(name: string, id: string)
|
||||
{
|
||||
if ( Known::use_service_store )
|
||||
return;
|
||||
|
||||
if ( Cluster::local_node_type() != Cluster::WORKER )
|
||||
return;
|
||||
|
||||
# Drop local suppression cache on workers to force HRW key repartitioning.
|
||||
Known::services = set();
|
||||
}
|
||||
|
||||
event Cluster::node_down(name: string, id: string)
|
||||
{
|
||||
if ( Known::use_service_store )
|
||||
return;
|
||||
|
||||
if ( Cluster::local_node_type() != Cluster::WORKER )
|
||||
return;
|
||||
|
||||
# Drop local suppression cache on workers to force HRW key repartitioning.
|
||||
Known::services = set();
|
||||
}
|
||||
|
||||
event service_info_commit(info: ServicesInfo)
|
||||
{
|
||||
if ( Known::use_service_store )
|
||||
return;
|
||||
|
||||
if ( [info$host, info$port_num] in Known::services )
|
||||
return;
|
||||
|
||||
local key = cat(info$host, info$port_num);
|
||||
Cluster::publish_hrw(Cluster::proxy_pool, key, known_service_add, info);
|
||||
event known_service_add(info);
|
||||
}
|
||||
|
||||
function known_services_done(c: connection)
|
||||
{
|
||||
local id = c$id;
|
||||
c$known_services_done = T;
|
||||
|
||||
if ( ! addr_matches_host(id$resp_h, service_tracking) ||
|
||||
"ftp-data" in c$service || # don't include ftp data sessions
|
||||
("DNS" in c$service && c$resp$size == 0) ) # for dns, require that the server talks.
|
||||
|
||||
if ( ! addr_matches_host(id$resp_h, service_tracking) )
|
||||
return;
|
||||
|
||||
# If no protocol was detected, wait a short
|
||||
# time before attempting to log in case a protocol is detected
|
||||
# on another connection.
|
||||
|
||||
if ( |c$service| == 1 )
|
||||
{
|
||||
if ( "ftp-data" in c$service )
|
||||
# Don't include ftp data sessions.
|
||||
return;
|
||||
|
||||
if ( "DNS" in c$service && c$resp$size == 0 )
|
||||
# For dns, require that the server talks.
|
||||
return;
|
||||
}
|
||||
|
||||
local info = ServicesInfo($ts = network_time(), $host = id$resp_h,
|
||||
$port_num = id$resp_p,
|
||||
$port_proto = get_port_transport_proto(id$resp_p),
|
||||
$service = c$service);
|
||||
|
||||
# If no protocol was detected, wait a short time before attempting to log
|
||||
# in case a protocol is detected on another connection.
|
||||
if ( |c$service| == 0 )
|
||||
schedule 5min { log_it(network_time(), id$resp_h, id$resp_p, c$service) };
|
||||
schedule 5min { service_info_commit(info) };
|
||||
else
|
||||
event log_it(network_time(), id$resp_h, id$resp_p, c$service);
|
||||
event service_info_commit(info);
|
||||
}
|
||||
|
||||
event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &priority=-5
|
||||
|
@ -96,6 +207,19 @@ event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &pr
|
|||
# Handle the connection ending in case no protocol was ever detected.
|
||||
event connection_state_remove(c: connection) &priority=-5
|
||||
{
|
||||
if ( ! c$known_services_done && c$resp$state == TCP_ESTABLISHED )
|
||||
known_services_done(c);
|
||||
if ( c$known_services_done )
|
||||
return;
|
||||
|
||||
if ( c$resp$state != TCP_ESTABLISHED )
|
||||
return;
|
||||
|
||||
known_services_done(c);
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Known::SERVICES_LOG, [$columns=ServicesInfo,
|
||||
$ev=log_known_services,
|
||||
$path="known_services"]);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
##! Tracks MAC address with hostnames seen in DHCP traffic. They are logged into
|
||||
##! ``devices.log``.
|
||||
|
||||
@load policy/misc/known-devices
|
||||
|
||||
module Known;
|
||||
|
||||
export {
|
||||
redef record DevicesInfo += {
|
||||
## The value of the DHCP host name option, if seen.
|
||||
dhcp_host_name: string &log &optional;
|
||||
};
|
||||
}
|
||||
|
||||
event dhcp_request(c: connection, msg: dhcp_msg, req_addr: addr, serv_addr: addr, host_name: string)
|
||||
{
|
||||
if ( msg$h_addr == "" )
|
||||
return;
|
||||
|
||||
if ( msg$h_addr !in known_devices )
|
||||
{
|
||||
add known_devices[msg$h_addr];
|
||||
Log::write(Known::DEVICES_LOG, [$ts=network_time(), $mac=msg$h_addr, $dhcp_host_name=host_name]);
|
||||
}
|
||||
}
|
||||
|
||||
event dhcp_inform(c: connection, msg: dhcp_msg, host_name: string)
|
||||
{
|
||||
if ( msg$h_addr == "" )
|
||||
return;
|
||||
|
||||
if ( msg$h_addr !in known_devices )
|
||||
{
|
||||
add known_devices[msg$h_addr];
|
||||
Log::write(Known::DEVICES_LOG, [$ts=network_time(), $mac=msg$h_addr, $dhcp_host_name=host_name]);
|
||||
}
|
||||
}
|
21
scripts/policy/protocols/dhcp/msg-orig.bro
Normal file
21
scripts/policy/protocols/dhcp/msg-orig.bro
Normal file
|
@ -0,0 +1,21 @@
|
|||
##! Add a field that logs the order of hosts sending messages
|
||||
##! using the same DHCP transaction ID. This information is
|
||||
##! occasionally needed on some networks to fully explain the
|
||||
##! DHCP sequence.
|
||||
|
||||
@load base/protocols/dhcp
|
||||
|
||||
module DHCP;
|
||||
|
||||
export {
|
||||
redef record DHCP::Info += {
|
||||
## The address that originated each message from the
|
||||
## `msg_types` field.
|
||||
msg_orig: vector of addr &log &default=addr_vec();
|
||||
};
|
||||
}
|
||||
|
||||
event DHCP::aggregate_msgs(ts: time, id: conn_id, uid: string, is_orig: bool, msg: DHCP::Msg, options: DHCP::Options) &priority=3
|
||||
{
|
||||
log_info$msg_orig += is_orig ? id$orig_h : id$resp_h;
|
||||
}
|
64
scripts/policy/protocols/dhcp/software.bro
Normal file
64
scripts/policy/protocols/dhcp/software.bro
Normal file
|
@ -0,0 +1,64 @@
|
|||
##! Software identification and extraction for DHCP traffic.
|
||||
|
||||
@load base/protocols/dhcp
|
||||
@load base/frameworks/software
|
||||
|
||||
module DHCP;
|
||||
|
||||
export {
|
||||
redef enum Software::Type += {
|
||||
## Identifier for web servers in the software framework.
|
||||
DHCP::SERVER,
|
||||
## Identifier for web browsers in the software framework.
|
||||
DHCP::CLIENT,
|
||||
};
|
||||
|
||||
redef record DHCP::Info += {
|
||||
## Software reported by the client in the `vendor_class` option.
|
||||
client_software: string &log &optional;
|
||||
## Software reported by the server in the `vendor_class` option.
|
||||
server_software: string &log &optional;
|
||||
};
|
||||
}
|
||||
|
||||
event DHCP::aggregate_msgs(ts: time, id: conn_id, uid: string, is_orig: bool, msg: DHCP::Msg, options: DHCP::Options) &priority=5
|
||||
{
|
||||
if ( options?$vendor_class )
|
||||
{
|
||||
if ( is_orig )
|
||||
log_info$client_software = options$vendor_class;
|
||||
else
|
||||
{
|
||||
log_info$server_software = options$vendor_class;
|
||||
Software::found(id, [$unparsed_version=options$vendor_class,
|
||||
$host=id$resp_h,
|
||||
$software_type=DHCP::SERVER]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event DHCP::log_dhcp(rec: DHCP::Info)
|
||||
{
|
||||
if ( rec?$assigned_addr && rec?$server_addr &&
|
||||
(rec?$client_software || rec?$server_software) )
|
||||
{
|
||||
local id: conn_id = [$orig_h=rec$assigned_addr,
|
||||
$orig_p=rec$client_port,
|
||||
$resp_h=rec$server_addr,
|
||||
$resp_p=rec$server_port];
|
||||
|
||||
if ( rec?$client_software && rec$assigned_addr != 255.255.255.255 )
|
||||
{
|
||||
Software::found(id, [$unparsed_version=rec$client_software,
|
||||
$host=rec$assigned_addr, $host_p=id$orig_p,
|
||||
$software_type=DHCP::CLIENT]);
|
||||
}
|
||||
|
||||
if ( rec?$server_software )
|
||||
{
|
||||
Software::found(id, [$unparsed_version=rec$server_software,
|
||||
$host=rec$server_addr, $host_p=id$resp_p,
|
||||
$software_type=DHCP::SERVER]);
|
||||
}
|
||||
}
|
||||
}
|
45
scripts/policy/protocols/dhcp/sub-opts.bro
Normal file
45
scripts/policy/protocols/dhcp/sub-opts.bro
Normal file
|
@ -0,0 +1,45 @@
|
|||
|
||||
@load base/protocols/dhcp
|
||||
|
||||
module DHCP;
|
||||
|
||||
export {
|
||||
redef record DHCP::Info += {
|
||||
## Added by DHCP relay agents which terminate switched or
|
||||
## permanent circuits. It encodes an agent-local identifier
|
||||
## of the circuit from which a DHCP client-to-server packet was
|
||||
## received. Typically it should represent a router or switch
|
||||
## interface number.
|
||||
circuit_id: string &log &optional;
|
||||
|
||||
## A globally unique identifier added by relay agents to identify
|
||||
## the remote host end of the circuit.
|
||||
agent_remote_id: string &log &optional;
|
||||
|
||||
## The subscriber ID is a value independent of the physical
|
||||
## network configuration so that a customer's DHCP configuration
|
||||
## can be given to them correctly no matter where they are
|
||||
## physically connected.
|
||||
subscriber_id: string &log &optional;
|
||||
};
|
||||
}
|
||||
|
||||
event DHCP::aggregate_msgs(ts: time, id: conn_id, uid: string, is_orig: bool, msg: DHCP::Msg, options: DHCP::Options)
|
||||
{
|
||||
if ( options?$sub_opt )
|
||||
{
|
||||
for ( i in options$sub_opt )
|
||||
{
|
||||
local sub_opt = options$sub_opt[i];
|
||||
|
||||
if ( sub_opt$code == 1 )
|
||||
DHCP::log_info$circuit_id = sub_opt$value;
|
||||
|
||||
else if ( sub_opt$code == 2 )
|
||||
DHCP::log_info$agent_remote_id = sub_opt$value;
|
||||
|
||||
else if ( sub_opt$code == 6 )
|
||||
DHCP::log_info$subscriber_id = sub_opt$value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,25 +31,40 @@ event signature_match(state: signature_state, msg: string, data: string) &priori
|
|||
local si: Software::Info;
|
||||
si = [$name=msg, $unparsed_version=msg, $host=c$id$resp_h, $host_p=c$id$resp_p, $software_type=WEB_APPLICATION];
|
||||
si$url = build_url_http(c$http);
|
||||
if ( c$id$resp_h in Software::tracked &&
|
||||
si$name in Software::tracked[c$id$resp_h] )
|
||||
{
|
||||
# If the new url is a substring of an existing, known url then let's
|
||||
# use that as the new url for the software.
|
||||
# PROBLEM: different version of the same software on the same server with a shared root path
|
||||
local is_substring = 0;
|
||||
if ( Software::tracked[c$id$resp_h][si$name]?$url &&
|
||||
|si$url| <= |Software::tracked[c$id$resp_h][si$name]$url| )
|
||||
is_substring = strstr(Software::tracked[c$id$resp_h][si$name]$url, si$url);
|
||||
|
||||
if ( is_substring == 1 )
|
||||
{
|
||||
Software::tracked[c$id$resp_h][si$name]$url = si$url;
|
||||
# Force the software to be logged because it indicates a URL
|
||||
# closer to the root of the site.
|
||||
si$force_log = T;
|
||||
}
|
||||
}
|
||||
|
||||
Software::found(c$id, si);
|
||||
}
|
||||
|
||||
event Software::register(info: Software::Info) &priority=5
|
||||
{
|
||||
if ( info$host !in Software::tracked )
|
||||
return;
|
||||
|
||||
local ss = Software::tracked[info$host];
|
||||
|
||||
if ( info$name !in ss )
|
||||
return;
|
||||
|
||||
local old_info = ss[info$name];
|
||||
|
||||
if ( ! old_info?$url )
|
||||
return;
|
||||
|
||||
if ( ! info?$url )
|
||||
return;
|
||||
|
||||
# If the new url is a substring of an existing, known url then let's
|
||||
# use that as the new url for the software.
|
||||
# PROBLEM: different version of the same software on the same server with a shared root path
|
||||
local is_substring = 0;
|
||||
|
||||
if ( |info$url| <= |old_info$url| )
|
||||
is_substring = strstr(old_info$url, info$url);
|
||||
|
||||
if ( is_substring != 1 )
|
||||
return;
|
||||
|
||||
old_info$url = info$url;
|
||||
# Force the software to be logged because it indicates a URL
|
||||
# closer to the root of the site.
|
||||
info$force_log = T;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ event http_header(c: connection, is_orig: bool, name: string, value: string) &pr
|
|||
{
|
||||
if ( ! c$http?$client_header_names )
|
||||
c$http$client_header_names = vector();
|
||||
c$http$client_header_names[|c$http$client_header_names|] = name;
|
||||
c$http$client_header_names += name;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -44,7 +44,7 @@ event http_header(c: connection, is_orig: bool, name: string, value: string) &pr
|
|||
{
|
||||
if ( ! c$http?$server_header_names )
|
||||
c$http$server_header_names = vector();
|
||||
c$http$server_header_names[|c$http$server_header_names|] = name;
|
||||
c$http$server_header_names += name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
33
scripts/policy/protocols/krb/ticket-logging.bro
Normal file
33
scripts/policy/protocols/krb/ticket-logging.bro
Normal file
|
@ -0,0 +1,33 @@
|
|||
##! Add Kerberos ticket hashes to the krb.log
|
||||
|
||||
@load base/protocols/krb
|
||||
|
||||
module KRB;
|
||||
|
||||
redef record Info += {
|
||||
## Hash of ticket used to authorize request/transaction
|
||||
auth_ticket: string &log &optional;
|
||||
## Hash of ticket returned by the KDC
|
||||
new_ticket: string &log &optional;
|
||||
};
|
||||
|
||||
event krb_ap_request(c: connection, ticket: KRB::Ticket, opts: KRB::AP_Options)
|
||||
{
|
||||
# Will be overwritten when request is a TGS
|
||||
c$krb$request_type = "AP";
|
||||
|
||||
if ( ticket?$ciphertext )
|
||||
c$krb$auth_ticket = md5_hash(ticket$ciphertext);
|
||||
}
|
||||
|
||||
event krb_as_response(c: connection, msg: KDC_Response)
|
||||
{
|
||||
if ( msg$ticket?$ciphertext )
|
||||
c$krb$new_ticket = md5_hash(msg$ticket$ciphertext);
|
||||
}
|
||||
|
||||
event krb_tgs_response(c: connection, msg: KDC_Response)
|
||||
{
|
||||
if ( msg$ticket?$ciphertext )
|
||||
c$krb$new_ticket = md5_hash(msg$ticket$ciphertext);
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
Support for SMB protocol analysis.
|
|
@ -1,8 +0,0 @@
|
|||
@load base/protocols/smb
|
||||
|
||||
@load ./main
|
||||
@load ./smb1-main
|
||||
@load ./smb2-main
|
||||
@load ./files
|
||||
|
||||
@load-sigs ./dpd.sig
|
|
@ -1,5 +0,0 @@
|
|||
signature dpd_smb {
|
||||
ip-proto == tcp
|
||||
payload /^....[\xfe\xff]SMB/
|
||||
enable "smb"
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
@load base/frameworks/files
|
||||
@load ./main
|
||||
|
||||
module SMB;
|
||||
|
||||
export {
|
||||
## Default file handle provider for SMB.
|
||||
global get_file_handle: function(c: connection, is_orig: bool): string;
|
||||
|
||||
## Default file describer for SMB.
|
||||
global describe_file: function(f: fa_file): string;
|
||||
}
|
||||
|
||||
function get_file_handle(c: connection, is_orig: bool): string
|
||||
{
|
||||
if ( ! (c$smb_state?$current_file &&
|
||||
(c$smb_state$current_file?$name ||
|
||||
c$smb_state$current_file?$path)) )
|
||||
{
|
||||
# TODO - figure out what are the cases where this happens.
|
||||
return "";
|
||||
}
|
||||
local current_file = c$smb_state$current_file;
|
||||
local path_name = current_file?$path ? current_file$path : "";
|
||||
local file_name = current_file?$name ? current_file$name : "";
|
||||
# Include last_mod time if available because if a file has been modified it
|
||||
# should be considered a new file.
|
||||
local last_mod = cat(current_file?$times ? current_file$times$modified : double_to_time(0.0));
|
||||
# TODO: This is doing hexdump to avoid problems due to file analysis handling
|
||||
# using CheckString which is not immune to encapsulated null bytes.
|
||||
# This needs to be fixed lower in the file analysis code later.
|
||||
return hexdump(cat(Analyzer::ANALYZER_SMB, c$id$orig_h, c$id$resp_h, path_name, file_name, last_mod));
|
||||
}
|
||||
|
||||
function describe_file(f: fa_file): string
|
||||
{
|
||||
# This shouldn't be needed, but just in case...
|
||||
if ( f$source != "SMB" )
|
||||
return "";
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
local info = f$conns[cid];
|
||||
if ( info?$smb_state && info$smb_state?$current_file && info$smb_state$current_file?$name )
|
||||
return info$smb_state$current_file$name;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Files::register_protocol(Analyzer::ANALYZER_SMB,
|
||||
[$get_file_handle = SMB::get_file_handle,
|
||||
$describe = SMB::describe_file]);
|
||||
}
|
||||
|
||||
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( c?$smb_state && c$smb_state?$current_file )
|
||||
{
|
||||
c$smb_state$current_file$fuid = f$id;
|
||||
|
||||
if ( c$smb_state$current_file$size > 0 )
|
||||
f$total_bytes = c$smb_state$current_file$size;
|
||||
|
||||
if ( c$smb_state$current_file?$name )
|
||||
f$info$filename = c$smb_state$current_file$name;
|
||||
}
|
||||
}
|
82
scripts/policy/protocols/smb/log-cmds.bro
Normal file
82
scripts/policy/protocols/smb/log-cmds.bro
Normal file
|
@ -0,0 +1,82 @@
|
|||
##! Load this script to generate an SMB command log, smb_cmd.log.
|
||||
##! This is primarily useful for debugging.
|
||||
|
||||
@load base/protocols/smb
|
||||
|
||||
module SMB;
|
||||
|
||||
export {
|
||||
redef enum Log::ID += {
|
||||
CMD_LOG,
|
||||
};
|
||||
|
||||
## The server response statuses which are *not* logged.
|
||||
const ignored_command_statuses: set[string] = {
|
||||
"MORE_PROCESSING_REQUIRED",
|
||||
} &redef;
|
||||
}
|
||||
|
||||
## Internal use only.
|
||||
## Some commands shouldn't be logged by the smb1_message event.
|
||||
const deferred_logging_cmds: set[string] = {
|
||||
"NEGOTIATE",
|
||||
"READ_ANDX",
|
||||
"SESSION_SETUP_ANDX",
|
||||
"TREE_CONNECT_ANDX",
|
||||
};
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(SMB::CMD_LOG, [$columns=SMB::CmdInfo, $path="smb_cmd"]);
|
||||
}
|
||||
|
||||
event smb1_message(c: connection, hdr: SMB1::Header, is_orig: bool) &priority=-5
|
||||
{
|
||||
if ( is_orig )
|
||||
return;
|
||||
|
||||
if ( c$smb_state$current_cmd$status in SMB::ignored_command_statuses )
|
||||
return;
|
||||
|
||||
if ( c$smb_state$current_cmd$command in SMB::deferred_logging_cmds )
|
||||
return;
|
||||
|
||||
Log::write(SMB::CMD_LOG, c$smb_state$current_cmd);
|
||||
}
|
||||
|
||||
event smb1_error(c: connection, hdr: SMB1::Header, is_orig: bool)
|
||||
{
|
||||
if ( is_orig )
|
||||
return;
|
||||
|
||||
# This is for deferred commands only.
|
||||
# The more specific messages won't fire for errors
|
||||
|
||||
if ( c$smb_state$current_cmd$status in SMB::ignored_command_statuses )
|
||||
return;
|
||||
|
||||
if ( c$smb_state$current_cmd$command !in SMB::deferred_logging_cmds )
|
||||
return;
|
||||
|
||||
Log::write(SMB::CMD_LOG, c$smb_state$current_cmd);
|
||||
}
|
||||
|
||||
event smb2_message(c: connection, hdr: SMB2::Header, is_orig: bool) &priority=-5
|
||||
{
|
||||
if ( is_orig )
|
||||
return;
|
||||
|
||||
# If the command that is being looked at right now was
|
||||
# marked as PENDING, then we'll skip all of this and wait
|
||||
# for a reply that isn't marked pending.
|
||||
if ( c$smb_state$current_cmd$status == "PENDING" )
|
||||
return;
|
||||
|
||||
if ( c$smb_state$current_cmd$status in SMB::ignored_command_statuses )
|
||||
return;
|
||||
|
||||
if ( c$smb_state$current_cmd$command in SMB::deferred_logging_cmds )
|
||||
return;
|
||||
|
||||
Log::write(SMB::CMD_LOG, c$smb_state$current_cmd);
|
||||
}
|
|
@ -1,267 +0,0 @@
|
|||
@load base/protocols/smb
|
||||
|
||||
module SMB;
|
||||
|
||||
export {
|
||||
redef enum Log::ID += {
|
||||
CMD_LOG,
|
||||
AUTH_LOG,
|
||||
MAPPING_LOG,
|
||||
FILES_LOG
|
||||
};
|
||||
|
||||
## Abstracted actions for SMB file actions.
|
||||
type Action: enum {
|
||||
FILE_READ,
|
||||
FILE_WRITE,
|
||||
FILE_OPEN,
|
||||
FILE_CLOSE,
|
||||
FILE_DELETE,
|
||||
FILE_RENAME,
|
||||
|
||||
PIPE_READ,
|
||||
PIPE_WRITE,
|
||||
PIPE_OPEN,
|
||||
PIPE_CLOSE,
|
||||
|
||||
PRINT_READ,
|
||||
PRINT_WRITE,
|
||||
PRINT_OPEN,
|
||||
PRINT_CLOSE,
|
||||
};
|
||||
|
||||
## The file actions which are logged.
|
||||
const logged_file_actions: set[Action] = {
|
||||
FILE_OPEN,
|
||||
FILE_RENAME,
|
||||
FILE_DELETE,
|
||||
|
||||
PRINT_OPEN,
|
||||
PRINT_CLOSE,
|
||||
} &redef;
|
||||
|
||||
## The server response statuses which are *not* logged.
|
||||
const ignored_command_statuses: set[string] = {
|
||||
"MORE_PROCESSING_REQUIRED",
|
||||
} &redef;
|
||||
|
||||
## This record is for the smb_files.log
|
||||
type FileInfo: record {
|
||||
## Time when the file was first discovered.
|
||||
ts : time &log;
|
||||
## Unique ID of the connection the file was sent over.
|
||||
uid : string &log;
|
||||
## ID of the connection the file was sent over.
|
||||
id : conn_id &log;
|
||||
## Unique ID of the file.
|
||||
fuid : string &log &optional;
|
||||
|
||||
## Action this log record represents.
|
||||
action : Action &log &optional;
|
||||
## Path pulled from the tree this file was transferred to or from.
|
||||
path : string &log &optional;
|
||||
## Filename if one was seen.
|
||||
name : string &log &optional;
|
||||
## Total size of the file.
|
||||
size : count &log &default=0;
|
||||
## If the rename action was seen, this will be
|
||||
## the file's previous name.
|
||||
prev_name : string &log &optional;
|
||||
## Last time this file was modified.
|
||||
times : SMB::MACTimes &log &optional;
|
||||
};
|
||||
|
||||
## This record is for the smb_mapping.log
|
||||
type TreeInfo: record {
|
||||
## Time when the tree was mapped.
|
||||
ts : time &log &optional;
|
||||
## Unique ID of the connection the tree was mapped over.
|
||||
uid : string &log;
|
||||
## ID of the connection the tree was mapped over.
|
||||
id : conn_id &log;
|
||||
|
||||
## Name of the tree path.
|
||||
path : string &log &optional;
|
||||
## The type of resource of the tree (disk share, printer share, named pipe, etc.).
|
||||
service : string &log &optional;
|
||||
## File system of the tree.
|
||||
native_file_system : string &log &optional;
|
||||
## If this is SMB2, a share type will be included. For SMB1,
|
||||
## the type of share will be deduced and included as well.
|
||||
share_type : string &log &default="DISK";
|
||||
};
|
||||
|
||||
## This record is for the smb_cmd.log
|
||||
type CmdInfo: record {
|
||||
## Timestamp of the command request.
|
||||
ts : time &log;
|
||||
## Unique ID of the connection the request was sent over.
|
||||
uid : string &log;
|
||||
## ID of the connection the request was sent over.
|
||||
id : conn_id &log;
|
||||
|
||||
## The command sent by the client.
|
||||
command : string &log;
|
||||
## The subcommand sent by the client, if present.
|
||||
sub_command : string &log &optional;
|
||||
## Command argument sent by the client, if any.
|
||||
argument : string &log &optional;
|
||||
|
||||
## Server reply to the client's command.
|
||||
status : string &log &optional;
|
||||
## Round trip time from the request to the response.
|
||||
rtt : interval &log &optional;
|
||||
## Version of SMB for the command.
|
||||
version : string &log;
|
||||
|
||||
## Authenticated username, if available.
|
||||
username : string &log &optional;
|
||||
|
||||
## If this is related to a tree, this is the tree
|
||||
## that was used for the current command.
|
||||
tree : string &log &optional;
|
||||
## The type of tree (disk share, printer share, named pipe, etc.).
|
||||
tree_service : string &log &optional;
|
||||
|
||||
## If the command referenced a file, store it here.
|
||||
referenced_file : FileInfo &log &optional;
|
||||
## If the command referenced a tree, store it here.
|
||||
referenced_tree : TreeInfo &optional;
|
||||
};
|
||||
|
||||
## This record stores the SMB state of in-flight commands,
|
||||
## the file and tree map of the connection.
|
||||
type State: record {
|
||||
## A reference to the current command.
|
||||
current_cmd : CmdInfo &optional;
|
||||
## A reference to the current file.
|
||||
current_file : FileInfo &optional;
|
||||
## A reference to the current tree.
|
||||
current_tree : TreeInfo &optional;
|
||||
|
||||
## Indexed on MID to map responses to requests.
|
||||
pending_cmds : table[count] of CmdInfo &optional;
|
||||
## File map to retrieve file information based on the file ID.
|
||||
fid_map : table[count] of FileInfo &optional;
|
||||
## Tree map to retrieve tree information based on the tree ID.
|
||||
tid_map : table[count] of TreeInfo &optional;
|
||||
## User map to retrieve user name based on the user ID.
|
||||
uid_map : table[count] of string &optional;
|
||||
## Pipe map to retrieve UUID based on the file ID of a pipe.
|
||||
pipe_map : table[count] of string &optional;
|
||||
|
||||
## A set of recent files to avoid logging the same
|
||||
## files over and over in the smb files log.
|
||||
## This only applies to files seen in a single connection.
|
||||
recent_files : set[string] &default=string_set() &read_expire=3min;
|
||||
};
|
||||
|
||||
## Optionally write out the SMB commands log. This is
|
||||
## primarily useful for debugging so is disabled by default.
|
||||
const write_cmd_log = F &redef;
|
||||
|
||||
## Everything below here is used internally in the SMB scripts.
|
||||
|
||||
redef record connection += {
|
||||
smb_state : State &optional;
|
||||
};
|
||||
|
||||
## Internal use only.
|
||||
## Some commands shouldn't be logged by the smb1_message event.
|
||||
const deferred_logging_cmds: set[string] = {
|
||||
"NEGOTIATE",
|
||||
"READ_ANDX",
|
||||
"SESSION_SETUP_ANDX",
|
||||
"TREE_CONNECT_ANDX",
|
||||
};
|
||||
|
||||
## This is an internally used function.
|
||||
const set_current_file: function(smb_state: State, file_id: count) &redef;
|
||||
|
||||
## This is an internally used function.
|
||||
const write_file_log: function(state: State) &redef;
|
||||
}
|
||||
|
||||
redef record FileInfo += {
|
||||
## ID referencing this file.
|
||||
fid : count &optional;
|
||||
|
||||
## UUID referencing this file if DCE/RPC.
|
||||
uuid : string &optional;
|
||||
};
|
||||
|
||||
const ports = { 139/tcp, 445/tcp };
|
||||
redef likely_server_ports += { ports };
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(SMB::CMD_LOG, [$columns=SMB::CmdInfo, $path="smb_cmd"]);
|
||||
Log::create_stream(SMB::FILES_LOG, [$columns=SMB::FileInfo, $path="smb_files"]);
|
||||
Log::create_stream(SMB::MAPPING_LOG, [$columns=SMB::TreeInfo, $path="smb_mapping"]);
|
||||
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_SMB, ports);
|
||||
}
|
||||
|
||||
function set_current_file(smb_state: State, file_id: count)
|
||||
{
|
||||
if ( file_id !in smb_state$fid_map )
|
||||
{
|
||||
smb_state$fid_map[file_id] = smb_state$current_cmd$referenced_file;
|
||||
smb_state$fid_map[file_id]$fid = file_id;
|
||||
}
|
||||
|
||||
smb_state$current_cmd$referenced_file = smb_state$fid_map[file_id];
|
||||
smb_state$current_file = smb_state$current_cmd$referenced_file;
|
||||
}
|
||||
|
||||
function write_file_log(state: State)
|
||||
{
|
||||
local f = state$current_file;
|
||||
if ( f?$name &&
|
||||
f$action in logged_file_actions )
|
||||
{
|
||||
# Everything in this if statement is to avoid overlogging
|
||||
# of the same data from a single connection based on recently
|
||||
# seen files in the SMB::State $recent_files field.
|
||||
if ( f?$times )
|
||||
{
|
||||
local file_ident = cat(f$action,
|
||||
f?$fuid ? f$fuid : "",
|
||||
f?$name ? f$name : "",
|
||||
f?$path ? f$path : "",
|
||||
f$size,
|
||||
f$times);
|
||||
if ( file_ident in state$recent_files )
|
||||
{
|
||||
# We've already seen this file and don't want to log it again.
|
||||
return;
|
||||
}
|
||||
else
|
||||
add state$recent_files[file_ident];
|
||||
}
|
||||
|
||||
Log::write(FILES_LOG, f);
|
||||
}
|
||||
}
|
||||
|
||||
event smb_pipe_connect_heuristic(c: connection) &priority=5
|
||||
{
|
||||
c$smb_state$current_tree$path = "<unknown>";
|
||||
c$smb_state$current_tree$share_type = "PIPE";
|
||||
}
|
||||
|
||||
event file_state_remove(f: fa_file) &priority=-5
|
||||
{
|
||||
if ( f$source != "SMB" )
|
||||
return;
|
||||
|
||||
for ( id in f$conns )
|
||||
{
|
||||
local c = f$conns[id];
|
||||
if ( c?$smb_state && c$smb_state?$current_file)
|
||||
{
|
||||
write_file_log(c$smb_state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -1,342 +0,0 @@
|
|||
@load ./main
|
||||
|
||||
module SMB1;
|
||||
|
||||
redef record SMB::CmdInfo += {
|
||||
## Dialects offered by the client.
|
||||
smb1_offered_dialects: string_vec &optional;
|
||||
};
|
||||
|
||||
event smb1_message(c: connection, hdr: SMB1::Header, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( ! c?$smb_state )
|
||||
{
|
||||
local state: SMB::State;
|
||||
state$fid_map = table();
|
||||
state$tid_map = table();
|
||||
state$uid_map = table();
|
||||
state$pipe_map = table();
|
||||
state$pending_cmds = table();
|
||||
c$smb_state = state;
|
||||
}
|
||||
|
||||
local smb_state = c$smb_state;
|
||||
local tid = hdr$tid;
|
||||
local uid = hdr$uid;
|
||||
local pid = hdr$pid;
|
||||
local mid = hdr$mid;
|
||||
|
||||
if ( uid in smb_state$uid_map )
|
||||
{
|
||||
smb_state$current_cmd$username = smb_state$uid_map[uid];
|
||||
}
|
||||
|
||||
if ( tid !in smb_state$tid_map )
|
||||
{
|
||||
smb_state$tid_map[tid] = SMB::TreeInfo($uid=c$uid, $id=c$id);
|
||||
}
|
||||
smb_state$current_tree = smb_state$tid_map[tid];
|
||||
if ( smb_state$current_tree?$path )
|
||||
{
|
||||
smb_state$current_cmd$tree = smb_state$current_tree$path;
|
||||
}
|
||||
|
||||
if ( smb_state$current_tree?$service )
|
||||
{
|
||||
smb_state$current_cmd$tree_service = smb_state$current_tree$service;
|
||||
}
|
||||
|
||||
if ( mid !in smb_state$pending_cmds )
|
||||
{
|
||||
local tmp_cmd = SMB::CmdInfo($ts=network_time(), $uid=c$uid, $id=c$id, $version="SMB1", $command = SMB1::commands[hdr$command]);
|
||||
|
||||
local tmp_file = SMB::FileInfo($ts=network_time(), $uid=c$uid, $id=c$id);
|
||||
tmp_cmd$referenced_file = tmp_file;
|
||||
tmp_cmd$referenced_tree = smb_state$current_tree;
|
||||
|
||||
smb_state$pending_cmds[mid] = tmp_cmd;
|
||||
}
|
||||
|
||||
smb_state$current_cmd = smb_state$pending_cmds[mid];
|
||||
|
||||
if ( !is_orig )
|
||||
{
|
||||
smb_state$current_cmd$rtt = network_time() - smb_state$current_cmd$ts;
|
||||
smb_state$current_cmd$status = SMB::statuses[hdr$status]$id;
|
||||
}
|
||||
}
|
||||
|
||||
event smb1_message(c: connection, hdr: SMB1::Header, is_orig: bool) &priority=-5
|
||||
{
|
||||
# Is this a response?
|
||||
if ( !is_orig )
|
||||
{
|
||||
if ( SMB::write_cmd_log &&
|
||||
c$smb_state$current_cmd$status !in SMB::ignored_command_statuses &&
|
||||
c$smb_state$current_cmd$command !in SMB::deferred_logging_cmds )
|
||||
{
|
||||
Log::write(SMB::CMD_LOG, c$smb_state$current_cmd);
|
||||
}
|
||||
delete c$smb_state$pending_cmds[hdr$mid];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
event smb1_transaction2_request(c: connection, hdr: SMB1::Header, sub_cmd: count)
|
||||
{
|
||||
c$smb_state$current_cmd$sub_command = SMB1::trans2_sub_commands[sub_cmd];
|
||||
}
|
||||
|
||||
|
||||
event smb1_negotiate_request(c: connection, hdr: SMB1::Header, dialects: string_vec) &priority=5
|
||||
{
|
||||
c$smb_state$current_cmd$smb1_offered_dialects = dialects;
|
||||
}
|
||||
|
||||
event smb1_negotiate_response(c: connection, hdr: SMB1::Header, response: SMB1::NegotiateResponse) &priority=5
|
||||
{
|
||||
if ( c$smb_state$current_cmd?$smb1_offered_dialects )
|
||||
{
|
||||
if ( response?$ntlm )
|
||||
{
|
||||
c$smb_state$current_cmd$argument = c$smb_state$current_cmd$smb1_offered_dialects[response$ntlm$dialect_index];
|
||||
}
|
||||
|
||||
delete c$smb_state$current_cmd$smb1_offered_dialects;
|
||||
}
|
||||
}
|
||||
|
||||
event smb1_negotiate_response(c: connection, hdr: SMB1::Header, response: SMB1::NegotiateResponse) &priority=-5
|
||||
{
|
||||
}
|
||||
|
||||
event smb1_tree_connect_andx_request(c: connection, hdr: SMB1::Header, path: string, service: string) &priority=5
|
||||
{
|
||||
local tmp_tree = SMB::TreeInfo($ts=network_time(), $uid=c$uid, $id=c$id, $path=path, $service=service);
|
||||
|
||||
c$smb_state$current_cmd$referenced_tree = tmp_tree;
|
||||
c$smb_state$current_cmd$argument = path;
|
||||
}
|
||||
|
||||
event smb1_tree_connect_andx_response(c: connection, hdr: SMB1::Header, service: string, native_file_system: string) &priority=5
|
||||
{
|
||||
c$smb_state$current_cmd$referenced_tree$service = service;
|
||||
if ( service == "IPC" )
|
||||
c$smb_state$current_cmd$referenced_tree$share_type = "PIPE";
|
||||
|
||||
c$smb_state$current_cmd$tree_service = service;
|
||||
|
||||
if ( native_file_system != "" )
|
||||
c$smb_state$current_cmd$referenced_tree$native_file_system = native_file_system;
|
||||
|
||||
c$smb_state$current_tree = c$smb_state$current_cmd$referenced_tree;
|
||||
c$smb_state$tid_map[hdr$tid] = c$smb_state$current_tree;
|
||||
}
|
||||
|
||||
event smb1_tree_connect_andx_response(c: connection, hdr: SMB1::Header, service: string, native_file_system: string) &priority=-5
|
||||
{
|
||||
Log::write(SMB::MAPPING_LOG, c$smb_state$current_tree);
|
||||
}
|
||||
|
||||
event smb1_nt_create_andx_request(c: connection, hdr: SMB1::Header, name: string) &priority=5
|
||||
{
|
||||
local tmp_file = SMB::FileInfo($ts=network_time(), $uid=c$uid, $id=c$id);
|
||||
c$smb_state$current_cmd$referenced_file = tmp_file;
|
||||
|
||||
c$smb_state$current_cmd$referenced_file$name = name;
|
||||
c$smb_state$current_cmd$referenced_file$action = SMB::FILE_OPEN;
|
||||
c$smb_state$current_file = c$smb_state$current_cmd$referenced_file;
|
||||
c$smb_state$current_cmd$argument = name;
|
||||
}
|
||||
|
||||
event smb1_nt_create_andx_response(c: connection, hdr: SMB1::Header, file_id: count, file_size: count, times: SMB::MACTimes) &priority=5
|
||||
{
|
||||
c$smb_state$current_cmd$referenced_file$action = SMB::FILE_OPEN;
|
||||
c$smb_state$current_cmd$referenced_file$fid = file_id;
|
||||
c$smb_state$current_cmd$referenced_file$size = file_size;
|
||||
|
||||
# I'm seeing negative data from IPC tree transfers
|
||||
if ( time_to_double(times$modified) > 0.0 )
|
||||
c$smb_state$current_cmd$referenced_file$times = times;
|
||||
|
||||
# We can identify the file by its file id now so let's stick it
|
||||
# in the file map.
|
||||
c$smb_state$fid_map[file_id] = c$smb_state$current_cmd$referenced_file;
|
||||
|
||||
c$smb_state$current_file = c$smb_state$fid_map[file_id];
|
||||
|
||||
SMB::write_file_log(c$smb_state);
|
||||
}
|
||||
|
||||
event smb1_read_andx_request(c: connection, hdr: SMB1::Header, file_id: count, offset: count, length: count) &priority=5
|
||||
{
|
||||
SMB::set_current_file(c$smb_state, file_id);
|
||||
c$smb_state$current_file$action = SMB::FILE_READ;
|
||||
if ( c$smb_state$current_file?$name )
|
||||
c$smb_state$current_cmd$argument = c$smb_state$current_file$name;
|
||||
}
|
||||
|
||||
event smb1_read_andx_request(c: connection, hdr: SMB1::Header, file_id: count, offset: count, length: count) &priority=-5
|
||||
{
|
||||
if ( c$smb_state$current_tree?$path && !c$smb_state$current_file?$path )
|
||||
c$smb_state$current_file$path = c$smb_state$current_tree$path;
|
||||
|
||||
SMB::write_file_log(c$smb_state);
|
||||
}
|
||||
|
||||
event smb1_write_andx_request(c: connection, hdr: SMB1::Header, file_id: count, offset: count, data_len: count) &priority=5
|
||||
{
|
||||
SMB::set_current_file(c$smb_state, file_id);
|
||||
c$smb_state$current_file$action = SMB::FILE_WRITE;
|
||||
if ( !c$smb_state$current_cmd?$argument &&
|
||||
# TODO: figure out why name isn't getting set sometimes.
|
||||
c$smb_state$current_file?$name )
|
||||
c$smb_state$current_cmd$argument = c$smb_state$current_file$name;
|
||||
}
|
||||
|
||||
event smb1_write_andx_request(c: connection, hdr: SMB1::Header, file_id: count, offset: count, data_len: count) &priority=-5
|
||||
{
|
||||
if ( c$smb_state$current_tree?$path && !c$smb_state$current_file?$path )
|
||||
c$smb_state$current_file$path = c$smb_state$current_tree$path;
|
||||
|
||||
# We don't even try to log reads and writes to the files log.
|
||||
#write_file_log(c$smb_state);
|
||||
}
|
||||
|
||||
#event smb1_write_andx_response(c: connection, hdr: SMB1::Header, written_bytes: count) &priority=5
|
||||
# {
|
||||
# # TODO - determine what to do here
|
||||
# }
|
||||
|
||||
event smb1_close_request(c: connection, hdr: SMB1::Header, file_id: count) &priority=5
|
||||
{
|
||||
SMB::set_current_file(c$smb_state, file_id);
|
||||
c$smb_state$current_file$action = SMB::FILE_CLOSE;
|
||||
}
|
||||
|
||||
event smb1_close_request(c: connection, hdr: SMB1::Header, file_id: count) &priority=-5
|
||||
{
|
||||
if ( file_id in c$smb_state$fid_map )
|
||||
{
|
||||
local fl = c$smb_state$fid_map[file_id];
|
||||
# Need to check for existence of path in case tree connect message wasn't seen.
|
||||
if ( c$smb_state$current_tree?$path )
|
||||
fl$path = c$smb_state$current_tree$path;
|
||||
|
||||
if ( fl?$name )
|
||||
c$smb_state$current_cmd$argument = fl$name;
|
||||
|
||||
delete c$smb_state$fid_map[file_id];
|
||||
|
||||
SMB::write_file_log(c$smb_state);
|
||||
}
|
||||
else
|
||||
{
|
||||
# TODO - Determine correct action
|
||||
# A reporter message is not right...
|
||||
#Reporter::warning("attempting to close an unknown file!");
|
||||
}
|
||||
}
|
||||
|
||||
event smb1_trans2_get_dfs_referral_request(c: connection, hdr: SMB1::Header, file_name: string)
|
||||
{
|
||||
c$smb_state$current_cmd$argument = file_name;
|
||||
}
|
||||
|
||||
event smb1_trans2_query_path_info_request(c: connection, hdr: SMB1::Header, file_name: string)
|
||||
{
|
||||
c$smb_state$current_cmd$argument = file_name;
|
||||
}
|
||||
|
||||
event smb1_trans2_find_first2_request(c: connection, hdr: SMB1::Header, args: SMB1::Find_First2_Request_Args)
|
||||
{
|
||||
c$smb_state$current_cmd$argument = args$file_name;
|
||||
}
|
||||
|
||||
event smb1_session_setup_andx_request(c: connection, hdr: SMB1::Header, request: SMB1::SessionSetupAndXRequest) &priority=5
|
||||
{
|
||||
# No behavior yet.
|
||||
}
|
||||
|
||||
event smb1_session_setup_andx_response(c: connection, hdr: SMB1::Header, response: SMB1::SessionSetupAndXResponse) &priority=-5
|
||||
{
|
||||
# No behavior yet.
|
||||
}
|
||||
|
||||
event smb1_transaction_request(c: connection, hdr: SMB1::Header, name: string, sub_cmd: count)
|
||||
{
|
||||
c$smb_state$current_cmd$sub_command = SMB1::trans_sub_commands[sub_cmd];
|
||||
}
|
||||
|
||||
event smb1_write_andx_request(c: connection, hdr: SMB1::Header, file_id: count, offset: count, data_len: count)
|
||||
{
|
||||
if ( ! c$smb_state?$current_file || ! c$smb_state$current_file?$uuid )
|
||||
{
|
||||
# TODO: figure out why the uuid isn't getting set sometimes.
|
||||
return;
|
||||
}
|
||||
|
||||
c$smb_state$pipe_map[file_id] = c$smb_state$current_file$uuid;
|
||||
}
|
||||
|
||||
event smb_pipe_bind_ack_response(c: connection, hdr: SMB1::Header)
|
||||
{
|
||||
if ( ! c$smb_state?$current_file || ! c$smb_state$current_file?$uuid )
|
||||
{
|
||||
# TODO: figure out why the uuid isn't getting set sometimes.
|
||||
return;
|
||||
}
|
||||
|
||||
c$smb_state$current_cmd$sub_command = "RPC_BIND_ACK";
|
||||
c$smb_state$current_cmd$argument = SMB::rpc_uuids[c$smb_state$current_file$uuid];
|
||||
}
|
||||
|
||||
event smb_pipe_bind_request(c: connection, hdr: SMB1::Header, uuid: string, version: string)
|
||||
{
|
||||
if ( ! c$smb_state?$current_file || ! c$smb_state$current_file?$uuid )
|
||||
{
|
||||
# TODO: figure out why the current_file isn't getting set sometimes.
|
||||
return;
|
||||
}
|
||||
|
||||
c$smb_state$current_cmd$sub_command = "RPC_BIND";
|
||||
c$smb_state$current_file$uuid = uuid;
|
||||
c$smb_state$current_cmd$argument = fmt("%s v%s", SMB::rpc_uuids[uuid], version);
|
||||
}
|
||||
|
||||
event smb_pipe_request(c: connection, hdr: SMB1::Header, op_num: count)
|
||||
{
|
||||
if ( ! c$smb_state?$current_file )
|
||||
{
|
||||
# TODO: figure out why the current file isn't being set sometimes.
|
||||
return;
|
||||
}
|
||||
|
||||
local f = c$smb_state$current_file;
|
||||
if ( ! f?$uuid )
|
||||
{
|
||||
# TODO: figure out why this is happening.
|
||||
event conn_weird("smb_pipe_request_missing_uuid", c, "");
|
||||
return;
|
||||
}
|
||||
local arg = fmt("%s: %s",
|
||||
SMB::rpc_uuids[f$uuid],
|
||||
SMB::rpc_sub_cmds[f$uuid][op_num]);
|
||||
|
||||
c$smb_state$current_cmd$argument = arg;
|
||||
}
|
||||
|
||||
event smb1_error(c: connection, hdr: SMB1::Header, is_orig: bool)
|
||||
{
|
||||
if ( ! is_orig )
|
||||
{
|
||||
# This is for deferred commands only.
|
||||
# The more specific messages won't fire for errors
|
||||
if ( SMB::write_cmd_log &&
|
||||
c$smb_state$current_cmd$status !in SMB::ignored_command_statuses &&
|
||||
c$smb_state$current_cmd$command in SMB::deferred_logging_cmds )
|
||||
{
|
||||
Log::write(SMB::CMD_LOG, c$smb_state$current_cmd);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
@load ./main
|
||||
|
||||
module SMB2;
|
||||
|
||||
redef record SMB::CmdInfo += {
|
||||
## Dialects offered by the client.
|
||||
smb2_offered_dialects: index_vec &optional;
|
||||
};
|
||||
|
||||
event smb2_message(c: connection, hdr: SMB2::Header, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( ! c?$smb_state )
|
||||
{
|
||||
local state: SMB::State;
|
||||
state$fid_map = table();
|
||||
state$tid_map = table();
|
||||
state$uid_map = table();
|
||||
state$pending_cmds = table();
|
||||
state$pipe_map = table();
|
||||
c$smb_state = state;
|
||||
}
|
||||
|
||||
local smb_state = c$smb_state;
|
||||
local tid = hdr$tree_id;
|
||||
local pid = hdr$process_id;
|
||||
local mid = hdr$message_id;
|
||||
local sid = hdr$session_id;
|
||||
|
||||
if ( mid !in smb_state$pending_cmds )
|
||||
{
|
||||
local tmp_file = SMB::FileInfo($ts=network_time(), $uid=c$uid, $id=c$id);
|
||||
local tmp_cmd = SMB::CmdInfo($ts=network_time(), $uid=c$uid, $id=c$id, $version="SMB2", $command = SMB2::commands[hdr$command]);
|
||||
tmp_cmd$referenced_file = tmp_file;
|
||||
smb_state$pending_cmds[mid] = tmp_cmd;
|
||||
}
|
||||
smb_state$current_cmd = smb_state$pending_cmds[mid];
|
||||
|
||||
if ( tid > 0 )
|
||||
{
|
||||
if ( smb_state$current_cmd?$referenced_tree )
|
||||
{
|
||||
smb_state$tid_map[tid] = smb_state$current_cmd$referenced_tree;
|
||||
}
|
||||
else if ( tid !in smb_state$tid_map )
|
||||
{
|
||||
local tmp_tree = SMB::TreeInfo($ts=network_time(), $uid=c$uid, $id=c$id);
|
||||
smb_state$tid_map[tid] = tmp_tree;
|
||||
}
|
||||
smb_state$current_cmd$referenced_tree = smb_state$tid_map[tid];
|
||||
}
|
||||
else
|
||||
{
|
||||
smb_state$current_cmd$referenced_tree = SMB::TreeInfo($ts=network_time(), $uid=c$uid, $id=c$id);
|
||||
}
|
||||
|
||||
smb_state$current_file = smb_state$current_cmd$referenced_file;
|
||||
smb_state$current_tree = smb_state$current_cmd$referenced_tree;
|
||||
|
||||
if ( !is_orig )
|
||||
{
|
||||
smb_state$current_cmd$rtt = network_time() - smb_state$current_cmd$ts;
|
||||
smb_state$current_cmd$status = SMB::statuses[hdr$status]$id;
|
||||
}
|
||||
}
|
||||
|
||||
event smb2_message(c: connection, hdr: SMB2::Header, is_orig: bool) &priority=-5
|
||||
{
|
||||
# Is this a response?
|
||||
if ( !is_orig )
|
||||
{
|
||||
if ( SMB::write_cmd_log &&
|
||||
c$smb_state$current_cmd$status !in SMB::ignored_command_statuses &&
|
||||
c$smb_state$current_cmd$command !in SMB::deferred_logging_cmds )
|
||||
{
|
||||
Log::write(SMB::CMD_LOG, c$smb_state$current_cmd);
|
||||
}
|
||||
delete c$smb_state$pending_cmds[hdr$message_id];
|
||||
}
|
||||
}
|
||||
|
||||
event smb2_negotiate_request(c: connection, hdr: SMB2::Header, dialects: index_vec) &priority=5
|
||||
{
|
||||
c$smb_state$current_cmd$smb2_offered_dialects = dialects;
|
||||
}
|
||||
|
||||
event smb2_negotiate_response(c: connection, hdr: SMB2::Header, response: SMB2::NegotiateResponse) &priority=5
|
||||
{
|
||||
if ( c$smb_state$current_cmd?$smb2_offered_dialects )
|
||||
{
|
||||
for ( i in c$smb_state$current_cmd$smb2_offered_dialects )
|
||||
{
|
||||
if ( response$dialect_revision == c$smb_state$current_cmd$smb2_offered_dialects[i] )
|
||||
{
|
||||
c$smb_state$current_cmd$argument = SMB2::dialects[response$dialect_revision];
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete c$smb_state$current_cmd$smb2_offered_dialects;
|
||||
}
|
||||
}
|
||||
|
||||
event smb2_negotiate_response(c: connection, hdr: SMB2::Header, response: SMB2::NegotiateResponse) &priority=5
|
||||
{
|
||||
# No behavior yet.
|
||||
}
|
||||
|
||||
event smb2_tree_connect_request(c: connection, hdr: SMB2::Header, path: string) &priority=5
|
||||
{
|
||||
c$smb_state$current_tree$path = path;
|
||||
}
|
||||
|
||||
event smb2_tree_connect_response(c: connection, hdr: SMB2::Header, response: SMB2::TreeConnectResponse) &priority=5
|
||||
{
|
||||
c$smb_state$current_tree$share_type = SMB2::share_types[response$share_type];
|
||||
}
|
||||
|
||||
event smb2_tree_connect_response(c: connection, hdr: SMB2::Header, response: SMB2::TreeConnectResponse) &priority=-5
|
||||
{
|
||||
Log::write(SMB::MAPPING_LOG, c$smb_state$current_tree);
|
||||
}
|
||||
|
||||
event smb2_tree_disconnect_request(c: connection, hdr: SMB2::Header) &priority=5
|
||||
{
|
||||
if ( hdr$tree_id in c$smb_state$tid_map )
|
||||
{
|
||||
delete c$smb_state$tid_map[hdr$tree_id];
|
||||
delete c$smb_state$current_tree;
|
||||
delete c$smb_state$current_cmd$referenced_tree;
|
||||
}
|
||||
}
|
||||
|
||||
event smb2_create_request(c: connection, hdr: SMB2::Header, name: string) &priority=5
|
||||
{
|
||||
if ( name == "")
|
||||
name = "<share_root>";
|
||||
|
||||
c$smb_state$current_file$name = name;
|
||||
|
||||
switch ( c$smb_state$current_tree$share_type )
|
||||
{
|
||||
case "DISK":
|
||||
c$smb_state$current_file$action = SMB::FILE_OPEN;
|
||||
break;
|
||||
case "PIPE":
|
||||
c$smb_state$current_file$action = SMB::PIPE_OPEN;
|
||||
break;
|
||||
case "PRINT":
|
||||
c$smb_state$current_file$action = SMB::PRINT_OPEN;
|
||||
break;
|
||||
default:
|
||||
c$smb_state$current_file$action = SMB::FILE_OPEN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
event smb2_create_response(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, file_size: count, times: SMB::MACTimes, attrs: SMB2::FileAttrs) &priority=5
|
||||
{
|
||||
SMB::set_current_file(c$smb_state, file_id$persistent+file_id$volatile);
|
||||
|
||||
c$smb_state$current_file$fid = file_id$persistent+file_id$volatile;
|
||||
c$smb_state$current_file$size = file_size;
|
||||
|
||||
if ( c$smb_state$current_tree?$path )
|
||||
c$smb_state$current_file$path = c$smb_state$current_tree$path;
|
||||
|
||||
# I'm seeing negative data from IPC tree transfers
|
||||
if ( time_to_double(times$modified) > 0.0 )
|
||||
c$smb_state$current_file$times = times;
|
||||
|
||||
# We can identify the file by its file id now so let's stick it
|
||||
# in the file map.
|
||||
c$smb_state$fid_map[file_id$persistent+file_id$volatile] = c$smb_state$current_file;
|
||||
|
||||
c$smb_state$current_file = c$smb_state$fid_map[file_id$persistent+file_id$volatile];
|
||||
}
|
||||
|
||||
event smb2_create_response(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, file_size: count, times: SMB::MACTimes, attrs: SMB2::FileAttrs) &priority=-5
|
||||
{
|
||||
SMB::write_file_log(c$smb_state);
|
||||
}
|
||||
|
||||
event smb2_read_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, offset: count, length: count) &priority=5
|
||||
{
|
||||
SMB::set_current_file(c$smb_state, file_id$persistent+file_id$volatile);
|
||||
|
||||
switch ( c$smb_state$current_tree$share_type )
|
||||
{
|
||||
case "DISK":
|
||||
c$smb_state$current_file$action = SMB::FILE_READ;
|
||||
break;
|
||||
case "PIPE":
|
||||
c$smb_state$current_file$action = SMB::PIPE_READ;
|
||||
break;
|
||||
case "PRINT":
|
||||
c$smb_state$current_file$action = SMB::PRINT_READ;
|
||||
break;
|
||||
default:
|
||||
c$smb_state$current_file$action = SMB::FILE_READ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
event smb2_read_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, offset: count, length: count) &priority=-5
|
||||
{
|
||||
SMB::write_file_log(c$smb_state);
|
||||
}
|
||||
|
||||
event smb2_write_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, offset: count, length: count) &priority=5
|
||||
{
|
||||
SMB::set_current_file(c$smb_state, file_id$persistent+file_id$volatile);
|
||||
|
||||
switch ( c$smb_state$current_tree$share_type )
|
||||
{
|
||||
case "DISK":
|
||||
c$smb_state$current_file$action = SMB::FILE_WRITE;
|
||||
break;
|
||||
case "PIPE":
|
||||
c$smb_state$current_file$action = SMB::PIPE_WRITE;
|
||||
break;
|
||||
case "PRINT":
|
||||
c$smb_state$current_file$action = SMB::PRINT_WRITE;
|
||||
break;
|
||||
default:
|
||||
c$smb_state$current_file$action = SMB::FILE_WRITE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
event smb2_write_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, offset: count, length: count) &priority=-5
|
||||
{
|
||||
SMB::write_file_log(c$smb_state);
|
||||
}
|
||||
|
||||
event smb2_file_rename(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, dst_filename: string) &priority=5
|
||||
{
|
||||
SMB::set_current_file(c$smb_state, file_id$persistent+file_id$volatile);
|
||||
|
||||
if ( c$smb_state$current_file?$name )
|
||||
c$smb_state$current_file$prev_name = c$smb_state$current_file$name;
|
||||
|
||||
c$smb_state$current_file$name = dst_filename;
|
||||
|
||||
switch ( c$smb_state$current_tree$share_type )
|
||||
{
|
||||
case "DISK":
|
||||
c$smb_state$current_file$action = SMB::FILE_RENAME;
|
||||
break;
|
||||
default:
|
||||
c$smb_state$current_file$action = SMB::FILE_RENAME;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
event smb2_file_rename(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, dst_filename: string) &priority=-5
|
||||
{
|
||||
SMB::write_file_log(c$smb_state);
|
||||
}
|
||||
|
||||
event smb2_file_delete(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, delete_pending: bool) &priority=5
|
||||
{
|
||||
SMB::set_current_file(c$smb_state, file_id$persistent+file_id$volatile);
|
||||
|
||||
if ( ! delete_pending )
|
||||
{
|
||||
# This is weird beause it would mean that someone didn't
|
||||
# set the delete bit in a delete request.
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( c$smb_state$current_tree$share_type )
|
||||
{
|
||||
case "DISK":
|
||||
c$smb_state$current_file$action = SMB::FILE_DELETE;
|
||||
break;
|
||||
default:
|
||||
c$smb_state$current_file$action = SMB::FILE_DELETE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
event smb2_file_delete(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, delete_pending: bool) &priority=-5
|
||||
{
|
||||
SMB::write_file_log(c$smb_state);
|
||||
}
|
||||
|
||||
event smb2_close_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID) &priority=5
|
||||
{
|
||||
SMB::set_current_file(c$smb_state, file_id$persistent+file_id$volatile);
|
||||
|
||||
switch ( c$smb_state$current_tree$share_type )
|
||||
{
|
||||
case "DISK":
|
||||
c$smb_state$current_file$action = SMB::FILE_CLOSE;
|
||||
break;
|
||||
case "PIPE":
|
||||
c$smb_state$current_file$action = SMB::PIPE_CLOSE;
|
||||
break;
|
||||
case "PRINT":
|
||||
c$smb_state$current_file$action = SMB::PRINT_CLOSE;
|
||||
break;
|
||||
default:
|
||||
c$smb_state$current_file$action = SMB::FILE_CLOSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
event smb2_close_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID) &priority=-5
|
||||
{
|
||||
if ( file_id$persistent+file_id$volatile in c$smb_state$fid_map )
|
||||
{
|
||||
local fl = c$smb_state$fid_map[file_id$persistent+file_id$volatile];
|
||||
# Need to check for existence of path in case tree connect message wasn't seen.
|
||||
if ( c$smb_state$current_tree?$path )
|
||||
fl$path = c$smb_state$current_tree$path;
|
||||
delete c$smb_state$fid_map[file_id$persistent+file_id$volatile];
|
||||
|
||||
SMB::write_file_log(c$smb_state);
|
||||
}
|
||||
else
|
||||
{
|
||||
# TODO - Determine correct action
|
||||
# A reporter message is not right...
|
||||
#Reporter::warning("attempting to close an unknown file!");
|
||||
}
|
||||
}
|
|
@ -50,33 +50,33 @@ 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];
|
||||
min_lengths_tls11 += [$cipher=/_AES_256_GCM_SHA384$/, $min_length=43];
|
||||
min_lengths_tls11 += [$cipher=/_AES_128_GCM_SHA256$/, $min_length=43];
|
||||
min_lengths_tls11 += [$cipher=/_256_CBC_SHA384$/, $min_length=96];
|
||||
min_lengths_tls11 += [$cipher=/_256_CBC_SHA256$/, $min_length=80];
|
||||
min_lengths_tls11 += [$cipher=/_256_CBC_SHA$/, $min_length=64];
|
||||
min_lengths_tls11 += [$cipher=/_128_CBC_SHA256$/, $min_length=80];
|
||||
min_lengths_tls11 += [$cipher=/_128_CBC_SHA$/, $min_length=64];
|
||||
min_lengths_tls11 += [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11 += [$cipher=/_SEED_CBC_SHA$/, $min_length=64];
|
||||
min_lengths_tls11 += [$cipher=/_IDEA_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11 += [$cipher=/_DES_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11 += [$cipher=/_DES40_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11 += [$cipher=/_RC4_128_SHA$/, $min_length=39];
|
||||
min_lengths_tls11 += [$cipher=/_RC4_128_MD5$/, $min_length=35];
|
||||
min_lengths_tls11 += [$cipher=/_RC4_40_MD5$/, $min_length=35];
|
||||
min_lengths_tls11 += [$cipher=/_RC2_CBC_40_MD5$/, $min_length=48];
|
||||
min_lengths += [$cipher=/_256_CBC_SHA$/, $min_length=48];
|
||||
min_lengths += [$cipher=/_128_CBC_SHA$/, $min_length=48];
|
||||
min_lengths += [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=40];
|
||||
min_lengths += [$cipher=/_SEED_CBC_SHA$/, $min_length=48];
|
||||
min_lengths += [$cipher=/_IDEA_CBC_SHA$/, $min_length=40];
|
||||
min_lengths += [$cipher=/_DES_CBC_SHA$/, $min_length=40];
|
||||
min_lengths += [$cipher=/_DES40_CBC_SHA$/, $min_length=40];
|
||||
min_lengths += [$cipher=/_RC4_128_SHA$/, $min_length=39];
|
||||
min_lengths += [$cipher=/_RC4_128_MD5$/, $min_length=35];
|
||||
min_lengths += [$cipher=/_RC4_40_MD5$/, $min_length=35];
|
||||
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)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
@load base/utils/directions-and-hosts
|
||||
@load base/protocols/ssl
|
||||
@load base/files/x509
|
||||
@load base/frameworks/cluster
|
||||
|
||||
module Known;
|
||||
|
||||
|
@ -29,27 +30,144 @@ export {
|
|||
## The certificates whose existence should be logged and tracked.
|
||||
## Choices are: LOCAL_HOSTS, REMOTE_HOSTS, ALL_HOSTS, NO_HOSTS.
|
||||
const cert_tracking = LOCAL_HOSTS &redef;
|
||||
|
||||
## Toggles between different implementations of this script.
|
||||
## When true, use a Broker data store, else use a regular Bro set
|
||||
## with keys uniformly distributed over proxy nodes in cluster
|
||||
## operation.
|
||||
const use_cert_store = T &redef;
|
||||
|
||||
type AddrCertHashPair: record {
|
||||
host: addr;
|
||||
hash: string;
|
||||
};
|
||||
|
||||
## Holds the set of all known certificates. Keys in the store are of
|
||||
## type :bro:type:`Known::AddrCertHashPair` and their associated value is
|
||||
## always the boolean value of "true".
|
||||
global cert_store: Cluster::StoreInfo;
|
||||
|
||||
## The Broker topic name to use for :bro:see:`Known::cert_store`.
|
||||
const cert_store_name = "bro/known/certs" &redef;
|
||||
|
||||
## The expiry interval of new entries in :bro:see:`Known::cert_store`.
|
||||
## This also changes the interval at which certs get logged.
|
||||
const cert_store_expiry = 1day &redef;
|
||||
|
||||
## The timeout interval to use for operations against
|
||||
## :bro:see:`Known::cert_store`.
|
||||
const cert_store_timeout = 15sec &redef;
|
||||
|
||||
## The set of all known certificates to store for preventing duplicate
|
||||
## 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' SHA1 hash.
|
||||
global certs: set[addr, string] &create_expire=1day &synchronized &redef;
|
||||
##
|
||||
## In cluster operation, this set is uniformly distributed across
|
||||
## proxy nodes.
|
||||
global certs: set[addr, string] &create_expire=1day &redef;
|
||||
|
||||
## 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);
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
event bro_init()
|
||||
{
|
||||
Log::create_stream(Known::CERTS_LOG, [$columns=CertsInfo, $ev=log_known_certs, $path="known_certs"]);
|
||||
if ( ! Known::use_cert_store )
|
||||
return;
|
||||
|
||||
Known::cert_store = Cluster::create_store(Known::cert_store_name);
|
||||
}
|
||||
|
||||
event Known::cert_found(info: CertsInfo, hash: string)
|
||||
{
|
||||
if ( ! Known::use_cert_store )
|
||||
return;
|
||||
|
||||
local key = AddrCertHashPair($host = info$host, $hash = hash);
|
||||
|
||||
when ( local r = Broker::put_unique(Known::cert_store$store, key,
|
||||
T, Known::cert_store_expiry) )
|
||||
{
|
||||
if ( r$status == Broker::SUCCESS )
|
||||
{
|
||||
if ( r$result as bool )
|
||||
Log::write(Known::CERTS_LOG, info);
|
||||
}
|
||||
else
|
||||
Reporter::error(fmt("%s: data store put_unique failure",
|
||||
Known::cert_store_name));
|
||||
}
|
||||
timeout Known::cert_store_timeout
|
||||
{
|
||||
# Can't really tell if master store ended up inserting a key.
|
||||
Log::write(Known::CERTS_LOG, info);
|
||||
}
|
||||
}
|
||||
|
||||
event known_cert_add(info: CertsInfo, hash: string)
|
||||
{
|
||||
if ( Known::use_cert_store )
|
||||
return;
|
||||
|
||||
if ( [info$host, hash] in Known::certs )
|
||||
return;
|
||||
|
||||
add Known::certs[info$host, hash];
|
||||
|
||||
@if ( ! Cluster::is_enabled() ||
|
||||
Cluster::local_node_type() == Cluster::PROXY )
|
||||
Log::write(Known::CERTS_LOG, info);
|
||||
@endif
|
||||
}
|
||||
|
||||
event Known::cert_found(info: CertsInfo, hash: string)
|
||||
{
|
||||
if ( Known::use_cert_store )
|
||||
return;
|
||||
|
||||
if ( [info$host, hash] in Known::certs )
|
||||
return;
|
||||
|
||||
local key = cat(info$host, hash);
|
||||
Cluster::publish_hrw(Cluster::proxy_pool, key, known_cert_add, info, hash);
|
||||
event known_cert_add(info, hash);
|
||||
}
|
||||
|
||||
event Cluster::node_up(name: string, id: string)
|
||||
{
|
||||
if ( Known::use_cert_store )
|
||||
return;
|
||||
|
||||
if ( Cluster::local_node_type() != Cluster::WORKER )
|
||||
return;
|
||||
|
||||
# Drop local suppression cache on workers to force HRW key repartitioning.
|
||||
Known::certs = table();
|
||||
}
|
||||
|
||||
event Cluster::node_down(name: string, id: string)
|
||||
{
|
||||
if ( Known::use_cert_store )
|
||||
return;
|
||||
|
||||
if ( Cluster::local_node_type() != Cluster::WORKER )
|
||||
return;
|
||||
|
||||
# Drop local suppression cache on workers to force HRW key repartitioning.
|
||||
Known::certs = table();
|
||||
}
|
||||
|
||||
event ssl_established(c: connection) &priority=3
|
||||
{
|
||||
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| < 1 ||
|
||||
! c$ssl$cert_chain[0]?$x509 )
|
||||
if ( ! c$ssl?$cert_chain )
|
||||
return;
|
||||
|
||||
if ( |c$ssl$cert_chain| < 1 )
|
||||
return;
|
||||
|
||||
if ( ! c$ssl$cert_chain[0]?$x509 )
|
||||
return;
|
||||
|
||||
local fuid = c$ssl$cert_chain_fuids[0];
|
||||
|
@ -61,16 +179,21 @@ event ssl_established(c: connection) &priority=3
|
|||
return;
|
||||
}
|
||||
|
||||
local host = c$id$resp_h;
|
||||
|
||||
if ( ! addr_matches_host(host, cert_tracking) )
|
||||
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,
|
||||
$serial=cert$serial]);
|
||||
}
|
||||
local info = CertsInfo($ts = network_time(), $host = host,
|
||||
$port_num = c$id$resp_p, $subject = cert$subject,
|
||||
$issuer_subject = cert$issuer,
|
||||
$serial = cert$serial);
|
||||
event Known::cert_found(info, hash);
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Known::CERTS_LOG, [$columns=CertsInfo, $ev=log_known_certs, $path="known_certs"]);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ module X509;
|
|||
|
||||
export {
|
||||
redef record Info += {
|
||||
# Logging is suppressed if field is set to F
|
||||
## Logging of certificate is suppressed if set to F
|
||||
logcert: bool &default=T;
|
||||
};
|
||||
}
|
||||
|
@ -39,14 +39,29 @@ event bro_init() &priority=2
|
|||
Log::add_filter(X509::LOG, f);
|
||||
}
|
||||
|
||||
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=2
|
||||
event file_sniff(f: fa_file, meta: fa_metadata) &priority=4
|
||||
{
|
||||
if ( ! c?$ssl )
|
||||
if ( |f$conns| != 1 )
|
||||
return;
|
||||
|
||||
if ( ! f?$info || ! f$info?$mime_type )
|
||||
return;
|
||||
|
||||
if ( ! ( f$info$mime_type == "application/x-x509-ca-cert" || f$info$mime_type == "application/x-x509-user-cert"
|
||||
|| f$info$mime_type == "application/pkix-cert" ) )
|
||||
return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
if ( ! f$conns[cid]?$ssl )
|
||||
return;
|
||||
|
||||
local c = f$conns[cid];
|
||||
}
|
||||
|
||||
local chain: vector of string;
|
||||
|
||||
if ( is_orig )
|
||||
if ( f$is_orig )
|
||||
chain = c$ssl$client_cert_chain_fuids;
|
||||
else
|
||||
chain = c$ssl$cert_chain_fuids;
|
||||
|
|
|
@ -56,7 +56,7 @@ event ssl_established(c: connection) &priority=3
|
|||
local waits_already = digest in waitlist;
|
||||
if ( ! waits_already )
|
||||
waitlist[digest] = vector();
|
||||
waitlist[digest][|waitlist[digest]|] = c$ssl;
|
||||
waitlist[digest] += c$ssl;
|
||||
if ( waits_already )
|
||||
return;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# Also caches all intermediate certificates encountered so far and use them
|
||||
# for future validations.
|
||||
|
||||
@load base/frameworks/cluster
|
||||
@load base/frameworks/notice
|
||||
@load base/protocols/ssl
|
||||
|
||||
|
@ -19,12 +20,17 @@ export {
|
|||
redef record Info += {
|
||||
## Result of certificate validation for this connection.
|
||||
validation_status: string &log &optional;
|
||||
## Result of certificate validation for this connection, given
|
||||
## as OpenSSL validation code.
|
||||
validation_code: int &optional;
|
||||
## Ordered chain of validated certificate, if validation succeeded.
|
||||
valid_chain: vector of opaque of x509 &optional;
|
||||
};
|
||||
|
||||
## MD5 hash values for recently validated chains along with the
|
||||
## Result values for recently validated chains along with the
|
||||
## validation status 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()
|
||||
global recently_validated_certs: table[string] of X509::Result = table()
|
||||
&read_expire=5mins &redef;
|
||||
|
||||
## Use intermediate CA certificate caching when trying to validate
|
||||
|
@ -39,11 +45,16 @@ export {
|
|||
## that you encounter. Only disable if you want to find misconfigured servers.
|
||||
global ssl_cache_intermediate_ca: bool = T &redef;
|
||||
|
||||
## Event from a worker to the manager that it has encountered a new
|
||||
## valid intermediate.
|
||||
## Store the valid chain in c$ssl$valid_chain if validation succeeds.
|
||||
## This has a potentially high memory impact, depending on the local environment
|
||||
## and is thus disabled by default.
|
||||
global ssl_store_valid_chain: bool = F &redef;
|
||||
|
||||
## Event from a manager to workers when encountering a new, valid
|
||||
## intermediate.
|
||||
global intermediate_add: event(key: string, value: vector of opaque of x509);
|
||||
|
||||
## Event from the manager to the workers that a new intermediate chain
|
||||
## Event from workers to the manager when a new intermediate chain
|
||||
## is to be added.
|
||||
global new_intermediate: event(key: string, value: vector of opaque of x509);
|
||||
}
|
||||
|
@ -51,12 +62,13 @@ export {
|
|||
global intermediate_cache: table[string] of vector of opaque of x509;
|
||||
|
||||
@if ( Cluster::is_enabled() )
|
||||
@load base/frameworks/cluster
|
||||
redef Cluster::manager2worker_events += /SSL::intermediate_add/;
|
||||
redef Cluster::worker2manager_events += /SSL::new_intermediate/;
|
||||
event bro_init()
|
||||
{
|
||||
Broker::auto_publish(Cluster::worker_topic, SSL::intermediate_add);
|
||||
Broker::auto_publish(Cluster::manager_topic, SSL::new_intermediate);
|
||||
}
|
||||
@endif
|
||||
|
||||
|
||||
function add_to_cache(key: string, value: vector of opaque of x509)
|
||||
{
|
||||
intermediate_cache[key] = value;
|
||||
|
@ -83,7 +95,7 @@ event SSL::new_intermediate(key: string, value: vector of opaque of x509)
|
|||
}
|
||||
@endif
|
||||
|
||||
function cache_validate(chain: vector of opaque of x509): string
|
||||
function cache_validate(chain: vector of opaque of x509): X509::Result
|
||||
{
|
||||
local chain_hash: vector of string = vector();
|
||||
|
||||
|
@ -97,7 +109,10 @@ function cache_validate(chain: vector of opaque of x509): string
|
|||
return recently_validated_certs[chain_id];
|
||||
|
||||
local result = x509_verify(chain, root_certs);
|
||||
recently_validated_certs[chain_id] = result$result_string;
|
||||
if ( ! ssl_store_valid_chain && result?$chain_certs )
|
||||
recently_validated_certs[chain_id] = X509::Result($result=result$result, $result_string=result$result_string);
|
||||
else
|
||||
recently_validated_certs[chain_id] = result;
|
||||
|
||||
# if we have a working chain where we did not store the intermediate certs
|
||||
# in our cache yet - do so
|
||||
|
@ -107,8 +122,8 @@ function cache_validate(chain: vector of opaque of x509): string
|
|||
|result$chain_certs| > 2 )
|
||||
{
|
||||
local result_chain = result$chain_certs;
|
||||
local icert = x509_parse(result_chain[1]);
|
||||
if ( icert$subject !in intermediate_cache )
|
||||
local isnh = x509_subject_name_hash(result_chain[1], 4); # SHA256
|
||||
if ( isnh !in intermediate_cache )
|
||||
{
|
||||
local cachechain: vector of opaque of x509;
|
||||
for ( i in result_chain )
|
||||
|
@ -116,14 +131,14 @@ function cache_validate(chain: vector of opaque of x509): string
|
|||
if ( i >=1 && i<=|result_chain|-2 )
|
||||
cachechain[i-1] = result_chain[i];
|
||||
}
|
||||
add_to_cache(icert$subject, cachechain);
|
||||
add_to_cache(isnh, cachechain);
|
||||
}
|
||||
}
|
||||
|
||||
return result$result_string;
|
||||
return result;
|
||||
}
|
||||
|
||||
event ssl_established(c: connection) &priority=3
|
||||
hook ssl_finishing(c: connection) &priority=20
|
||||
{
|
||||
# If there aren't any certs we can't very well do certificate validation.
|
||||
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 ||
|
||||
|
@ -131,23 +146,26 @@ event ssl_established(c: connection) &priority=3
|
|||
return;
|
||||
|
||||
local intermediate_chain: vector of opaque of x509 = vector();
|
||||
local issuer = c$ssl$cert_chain[0]$x509$certificate$issuer;
|
||||
local issuer_name_hash = x509_issuer_name_hash(c$ssl$cert_chain[0]$x509$handle, 4); # SHA256
|
||||
local hash = c$ssl$cert_chain[0]$sha1;
|
||||
local result: string;
|
||||
local result: X509::Result;
|
||||
|
||||
# Look if we already have a working chain for the issuer of this cert.
|
||||
# If yes, try this chain first instead of using the chain supplied from
|
||||
# the server.
|
||||
if ( ssl_cache_intermediate_ca && issuer in intermediate_cache )
|
||||
if ( ssl_cache_intermediate_ca && issuer_name_hash in intermediate_cache )
|
||||
{
|
||||
intermediate_chain[0] = c$ssl$cert_chain[0]$x509$handle;
|
||||
for ( i in intermediate_cache[issuer] )
|
||||
intermediate_chain[i+1] = intermediate_cache[issuer][i];
|
||||
for ( i in intermediate_cache[issuer_name_hash] )
|
||||
intermediate_chain[i+1] = intermediate_cache[issuer_name_hash][i];
|
||||
|
||||
result = cache_validate(intermediate_chain);
|
||||
if ( result == "ok" )
|
||||
if ( result$result_string == "ok" )
|
||||
{
|
||||
c$ssl$validation_status = result;
|
||||
c$ssl$validation_status = result$result_string;
|
||||
c$ssl$validation_code = result$result;
|
||||
if ( result?$chain_certs )
|
||||
c$ssl$valid_chain = result$chain_certs;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -163,13 +181,16 @@ event ssl_established(c: connection) &priority=3
|
|||
}
|
||||
|
||||
result = cache_validate(chain);
|
||||
c$ssl$validation_status = result;
|
||||
c$ssl$validation_status = result$result_string;
|
||||
c$ssl$validation_code = result$result;
|
||||
if ( result?$chain_certs )
|
||||
c$ssl$valid_chain = result$chain_certs;
|
||||
|
||||
if ( result != "ok" )
|
||||
if ( result$result_string != "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,hash,c$ssl$validation_status)]);
|
||||
$sub=c$ssl$cert_chain[0]$x509$certificate$subject, $conn=c,
|
||||
$identifier=cat(c$id$resp_h,c$id$resp_p,hash,c$ssl$validation_code)]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
##! Perform OCSP response validation.
|
||||
##! Perform validation of stapled OCSP responses.
|
||||
#!
|
||||
#! Note: this _only_ performs validation of stapled OCSP responsed. It does
|
||||
#! not validate OCSP responses that are retrieved via HTTP, because we do not
|
||||
#! have a mapping to certificates.
|
||||
|
||||
|
||||
@load base/frameworks/notice
|
||||
@load base/protocols/ssl
|
||||
|
@ -15,7 +20,6 @@ export {
|
|||
redef record Info += {
|
||||
## Result of ocsp validation for this connection.
|
||||
ocsp_status: string &log &optional;
|
||||
|
||||
## ocsp response as string.
|
||||
ocsp_response: string &optional;
|
||||
};
|
||||
|
|
212
scripts/policy/protocols/ssl/validate-sct.bro
Normal file
212
scripts/policy/protocols/ssl/validate-sct.bro
Normal file
|
@ -0,0 +1,212 @@
|
|||
##! Perform validation of Signed Certificate Timestamps, as used
|
||||
##! for Certificate Transparency. See RFC6962 for more details.
|
||||
|
||||
@load base/protocols/ssl
|
||||
@load protocols/ssl/validate-certs
|
||||
|
||||
# We need to know issuer certificates to be able to determine the IssuerKeyHash,
|
||||
# which is required for validating certificate extensions.
|
||||
redef SSL::ssl_store_valid_chain = T;
|
||||
|
||||
module SSL;
|
||||
|
||||
export {
|
||||
|
||||
## List of the different sources for Signed Certificate Timestamp
|
||||
type SctSource: enum {
|
||||
## Signed Certificate Timestamp was encountered in the extension of
|
||||
## an X.509 certificate.
|
||||
SCT_X509_EXT,
|
||||
## Signed Certificate Timestamp was encountered in an TLS session
|
||||
## extension.
|
||||
SCT_TLS_EXT,
|
||||
## Signed Certificate Timestamp was encountered in the extension of
|
||||
## an stapled OCSP reply.
|
||||
SCT_OCSP_EXT
|
||||
};
|
||||
|
||||
## This record is used to store information about the SCTs that are
|
||||
## encountered in a SSL connection.
|
||||
type SctInfo: record {
|
||||
## The version of the encountered SCT (should always be 0 for v1).
|
||||
version: count;
|
||||
## The ID of the log issuing this SCT.
|
||||
logid: string;
|
||||
## The timestamp at which this SCT was issued measured since the
|
||||
## epoch (January 1, 1970, 00:00), ignoring leap seconds, in
|
||||
## milliseconds. Not converted to a Bro timestamp because we need
|
||||
## the exact value for validation.
|
||||
timestamp: count;
|
||||
## The signature algorithm used for this sct.
|
||||
sig_alg: count;
|
||||
## The hash algorithm used for this sct.
|
||||
hash_alg: count;
|
||||
## The signature of this SCT.
|
||||
signature: string;
|
||||
## Source of this SCT.
|
||||
source: SctSource;
|
||||
## Validation result of this SCT.
|
||||
valid: bool &optional;
|
||||
};
|
||||
|
||||
redef record Info += {
|
||||
## Number of valid SCTs that were encountered in the connection.
|
||||
valid_scts: count &optional;
|
||||
## Number of SCTs that could not be validated that were encountered in the connection.
|
||||
invalid_scts: count &optional;
|
||||
## Number of different Logs for which valid SCTs were encountered in the connection.
|
||||
valid_ct_logs: count &log &optional;
|
||||
## Number of different Log operators of which valid SCTs were encountered in the connection.
|
||||
valid_ct_operators: count &log &optional;
|
||||
## List of operators for which valid SCTs were encountered in the connection.
|
||||
valid_ct_operators_list: set[string] &optional;
|
||||
## Information about all SCTs that were encountered in the connection.
|
||||
ct_proofs: vector of SctInfo &default=vector();
|
||||
};
|
||||
}
|
||||
|
||||
# Used to cache validations for 5 minutes to lessen computational load.
|
||||
global recently_validated_scts: table[string] of bool = table()
|
||||
&read_expire=5mins &redef;
|
||||
|
||||
event bro_init()
|
||||
{
|
||||
Files::register_for_mime_type(Files::ANALYZER_OCSP_REPLY, "application/ocsp-response");
|
||||
}
|
||||
|
||||
event ssl_extension_signed_certificate_timestamp(c: connection, is_orig: bool, version: count, logid: string, timestamp: count, signature_and_hashalgorithm: SSL::SignatureAndHashAlgorithm, signature: string) &priority=5
|
||||
{
|
||||
c$ssl$ct_proofs += SctInfo($version=version, $logid=logid, $timestamp=timestamp, $sig_alg=signature_and_hashalgorithm$SignatureAlgorithm, $hash_alg=signature_and_hashalgorithm$HashAlgorithm, $signature=signature, $source=SCT_TLS_EXT);
|
||||
}
|
||||
|
||||
event x509_ocsp_ext_signed_certificate_timestamp(f: fa_file, version: count, logid: string, timestamp: count, hash_algorithm: count, signature_algorithm: count, signature: string) &priority=5
|
||||
{
|
||||
local src: SctSource;
|
||||
if ( ! f?$info )
|
||||
return;
|
||||
|
||||
if ( f$source == "SSL" && f$info$mime_type == "application/ocsp-response" )
|
||||
src = SCT_OCSP_EXT;
|
||||
else if ( f$source == "SSL" && f$info$mime_type == "application/x-x509-user-cert" )
|
||||
src = SCT_X509_EXT;
|
||||
else
|
||||
return;
|
||||
|
||||
if ( |f$conns| != 1 )
|
||||
return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
if ( ! f$conns[cid]?$ssl )
|
||||
return;
|
||||
|
||||
local c = f$conns[cid];
|
||||
}
|
||||
|
||||
c$ssl$ct_proofs += SctInfo($version=version, $logid=logid, $timestamp=timestamp, $sig_alg=signature_algorithm, $hash_alg=hash_algorithm, $signature=signature, $source=src);
|
||||
}
|
||||
|
||||
# Priority = 19 will be handled after validation is done
|
||||
hook ssl_finishing(c: connection) &priority=19
|
||||
{
|
||||
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 || ! c$ssl$cert_chain[0]?$x509 )
|
||||
return;
|
||||
|
||||
local cert = c$ssl$cert_chain[0]$x509$handle;
|
||||
local certhash = c$ssl$cert_chain[0]$sha1;
|
||||
local issuer_name_hash = x509_issuer_name_hash(cert, 4);
|
||||
local valid_proofs = 0;
|
||||
local invalid_proofs = 0;
|
||||
c$ssl$valid_ct_operators_list = string_set();
|
||||
local valid_logs = string_set();
|
||||
local issuer_key_hash = "";
|
||||
|
||||
for ( i in c$ssl$ct_proofs )
|
||||
{
|
||||
local proof = c$ssl$ct_proofs[i];
|
||||
if ( proof$logid !in SSL::ct_logs )
|
||||
{
|
||||
# Well, if we don't know the log, there is nothing to do here...
|
||||
proof$valid = F;
|
||||
next;
|
||||
}
|
||||
local log = SSL::ct_logs[proof$logid];
|
||||
|
||||
local valid = F;
|
||||
local found_cache = F;
|
||||
|
||||
local validatestring = cat(certhash,proof$logid,proof$timestamp,proof$hash_alg,proof$signature,proof$source);
|
||||
if ( proof$source == SCT_X509_EXT && c$ssl?$validation_code )
|
||||
validatestring = cat(validatestring, c$ssl$validation_code);
|
||||
local validate_hash = sha1_hash(validatestring);
|
||||
if ( validate_hash in recently_validated_scts )
|
||||
{
|
||||
valid = recently_validated_scts[validate_hash];
|
||||
found_cache = T;
|
||||
}
|
||||
|
||||
if ( found_cache == F && ( proof$source == SCT_TLS_EXT || proof$source == SCT_OCSP_EXT ) )
|
||||
{
|
||||
valid = sct_verify(cert, proof$logid, log$key, proof$signature, proof$timestamp, proof$hash_alg);
|
||||
}
|
||||
else if ( found_cache == F )
|
||||
{
|
||||
# X.509 proof. Here things get awkward because we need information about
|
||||
# the issuer cert... and we need to try a few times, because we have to see if we got
|
||||
# the right issuer cert.
|
||||
#
|
||||
# First - Let's try if a previous round already established the correct issuer key hash.
|
||||
if ( issuer_key_hash != "" )
|
||||
{
|
||||
valid = sct_verify(cert, proof$logid, log$key, proof$signature, proof$timestamp, proof$hash_alg, issuer_key_hash);
|
||||
}
|
||||
|
||||
# Second - let's see if we might already know the issuer cert through verification.
|
||||
if ( ! valid && issuer_name_hash in intermediate_cache )
|
||||
{
|
||||
issuer_key_hash = x509_spki_hash(intermediate_cache[issuer_name_hash][0], 4);
|
||||
valid = sct_verify(cert, proof$logid, log$key, proof$signature, proof$timestamp, proof$hash_alg, issuer_key_hash);
|
||||
}
|
||||
if ( ! valid && c$ssl?$valid_chain && |c$ssl$valid_chain| >= 2 )
|
||||
{
|
||||
issuer_key_hash = x509_spki_hash(c$ssl$valid_chain[1], 4);
|
||||
valid = sct_verify(cert, proof$logid, log$key, proof$signature, proof$timestamp, proof$hash_alg, issuer_key_hash);
|
||||
}
|
||||
|
||||
# ok, if it still did not work - let's just try with all the certs that were sent
|
||||
# in the connection. Perhaps it will work with one of them.
|
||||
if ( !valid )
|
||||
for ( i in c$ssl$cert_chain )
|
||||
{
|
||||
if ( i == 0 ) # end-host-cert
|
||||
next;
|
||||
if ( ! c$ssl$cert_chain[i]?$x509 || ! c$ssl$cert_chain[i]$x509?$handle )
|
||||
next;
|
||||
|
||||
issuer_key_hash = x509_spki_hash(c$ssl$cert_chain[i]$x509$handle, 4);
|
||||
valid = sct_verify(cert, proof$logid, log$key, proof$signature, proof$timestamp, proof$hash_alg, issuer_key_hash);
|
||||
if ( valid )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! found_cache )
|
||||
recently_validated_scts[validate_hash] = valid;
|
||||
|
||||
proof$valid = valid;
|
||||
|
||||
if ( valid )
|
||||
{
|
||||
++valid_proofs;
|
||||
add c$ssl$valid_ct_operators_list[log$operator];
|
||||
add valid_logs[proof$logid];
|
||||
}
|
||||
else
|
||||
++invalid_proofs;
|
||||
}
|
||||
|
||||
c$ssl$valid_scts = valid_proofs;
|
||||
c$ssl$invalid_scts = invalid_proofs;
|
||||
c$ssl$valid_ct_operators = |c$ssl$valid_ct_operators_list|;
|
||||
c$ssl$valid_ct_logs = |valid_logs|;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue