mirror of
https://github.com/zeek/zeek.git
synced 2025-10-13 20:18:20 +00:00
Merge remote-tracking branch 'origin/master' into topic/vladg/file-analysis-exe-analyzer
This commit is contained in:
commit
010cd80f2f
546 changed files with 19787 additions and 4199 deletions
|
@ -53,7 +53,8 @@ function set_limit(f: fa_file, args: Files::AnalyzerArgs, n: count): bool
|
|||
function on_add(f: fa_file, args: Files::AnalyzerArgs)
|
||||
{
|
||||
if ( ! args?$extract_filename )
|
||||
args$extract_filename = cat("extract-", f$source, "-", f$id);
|
||||
args$extract_filename = cat("extract-", f$last_active, "-", f$source,
|
||||
"-", f$id);
|
||||
|
||||
f$info$extracted = args$extract_filename;
|
||||
args$extract_filename = build_path_compressed(prefix, args$extract_filename);
|
||||
|
|
|
@ -152,26 +152,26 @@ redef record fa_file += {
|
|||
|
||||
event Unified2::read_sid_msg_line(desc: Input::EventDescription, tpe: Input::Event, line: string)
|
||||
{
|
||||
local parts = split_n(line, / \|\| /, F, 100);
|
||||
if ( |parts| >= 2 && /^[0-9]+$/ in parts[1] )
|
||||
sid_map[to_count(parts[1])] = parts[2];
|
||||
local parts = split_string_n(line, / \|\| /, F, 100);
|
||||
if ( |parts| >= 2 && /^[0-9]+$/ in parts[0] )
|
||||
sid_map[to_count(parts[0])] = parts[1];
|
||||
}
|
||||
|
||||
event Unified2::read_gen_msg_line(desc: Input::EventDescription, tpe: Input::Event, line: string)
|
||||
{
|
||||
local parts = split_n(line, / \|\| /, F, 3);
|
||||
if ( |parts| >= 2 && /^[0-9]+$/ in parts[1] )
|
||||
gen_map[to_count(parts[1])] = parts[3];
|
||||
local parts = split_string_n(line, / \|\| /, F, 3);
|
||||
if ( |parts| >= 2 && /^[0-9]+$/ in parts[0] )
|
||||
gen_map[to_count(parts[0])] = parts[2];
|
||||
}
|
||||
|
||||
event Unified2::read_classification_line(desc: Input::EventDescription, tpe: Input::Event, line: string)
|
||||
{
|
||||
local parts = split_n(line, /: /, F, 2);
|
||||
local parts = split_string_n(line, /: /, F, 2);
|
||||
if ( |parts| == 2 )
|
||||
{
|
||||
local parts2 = split_n(parts[2], /,/, F, 4);
|
||||
local parts2 = split_string_n(parts[1], /,/, F, 4);
|
||||
if ( |parts2| > 1 )
|
||||
classification_map[|classification_map|+1] = parts2[1];
|
||||
classification_map[|classification_map|+1] = parts2[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ event Input::end_of_data(name: string, source: string)
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Unified2::LOG, [$columns=Info, $ev=log_unified2]);
|
||||
Log::create_stream(Unified2::LOG, [$columns=Info, $ev=log_unified2, $path="unified2"]);
|
||||
|
||||
if ( sid_msg == "" )
|
||||
{
|
||||
|
@ -249,9 +249,9 @@ event bro_init() &priority=5
|
|||
event file_new(f: fa_file)
|
||||
{
|
||||
local file_dir = "";
|
||||
local parts = split_all(f$source, /\/[^\/]*$/);
|
||||
local parts = split_string_all(f$source, /\/[^\/]*$/);
|
||||
if ( |parts| == 3 )
|
||||
file_dir = parts[1];
|
||||
file_dir = parts[0];
|
||||
|
||||
if ( (watch_file != "" && f$source == watch_file) ||
|
||||
(watch_dir != "" && compress_path(watch_dir) == file_dir) )
|
||||
|
|
|
@ -36,7 +36,7 @@ export {
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(X509::LOG, [$columns=Info, $ev=log_x509]);
|
||||
Log::create_stream(X509::LOG, [$columns=Info, $ev=log_x509, $path="x509"]);
|
||||
}
|
||||
|
||||
redef record Files::Info += {
|
||||
|
|
1
scripts/base/frameworks/broker/__load__.bro
Normal file
1
scripts/base/frameworks/broker/__load__.bro
Normal file
|
@ -0,0 +1 @@
|
|||
@load ./main
|
103
scripts/base/frameworks/broker/main.bro
Normal file
103
scripts/base/frameworks/broker/main.bro
Normal file
|
@ -0,0 +1,103 @@
|
|||
##! Various data structure definitions for use with Bro's communication system.
|
||||
|
||||
module BrokerComm;
|
||||
|
||||
export {
|
||||
|
||||
## A name used to identify this endpoint to peers.
|
||||
## .. bro:see:: BrokerComm::connect BrokerComm::listen
|
||||
const endpoint_name = "" &redef;
|
||||
|
||||
## Change communication behavior.
|
||||
type EndpointFlags: record {
|
||||
## Whether to restrict message topics that can be published to peers.
|
||||
auto_publish: bool &default = T;
|
||||
## Whether to restrict what message topics or data store identifiers
|
||||
## the local endpoint advertises to peers (e.g. subscribing to
|
||||
## events or making a master data store available).
|
||||
auto_advertise: bool &default = T;
|
||||
};
|
||||
|
||||
## Fine-grained tuning of communication behavior for a particular message.
|
||||
type SendFlags: record {
|
||||
## Send the message to the local endpoint.
|
||||
self: bool &default = F;
|
||||
## Send the message to peer endpoints that advertise interest in
|
||||
## the topic associated with the message.
|
||||
peers: bool &default = T;
|
||||
## Send the message to peer endpoints even if they don't advertise
|
||||
## interest in the topic associated with the message.
|
||||
unsolicited: bool &default = F;
|
||||
};
|
||||
|
||||
## Opaque communication data.
|
||||
type Data: record {
|
||||
d: opaque of BrokerComm::Data &optional;
|
||||
};
|
||||
|
||||
## Opaque communication data.
|
||||
type DataVector: vector of BrokerComm::Data;
|
||||
|
||||
## Opaque event communication data.
|
||||
type EventArgs: record {
|
||||
## The name of the event. Not set if invalid event or arguments.
|
||||
name: string &optional;
|
||||
## The arguments to the event.
|
||||
args: DataVector;
|
||||
};
|
||||
|
||||
## Opaque communication data used as a convenient way to wrap key-value
|
||||
## pairs that comprise table entries.
|
||||
type TableItem : record {
|
||||
key: BrokerComm::Data;
|
||||
val: BrokerComm::Data;
|
||||
};
|
||||
}
|
||||
|
||||
module BrokerStore;
|
||||
|
||||
export {
|
||||
|
||||
## Whether a data store query could be completed or not.
|
||||
type QueryStatus: enum {
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
};
|
||||
|
||||
## An expiry time for a key-value pair inserted in to a data store.
|
||||
type ExpiryTime: record {
|
||||
## Absolute point in time at which to expire the entry.
|
||||
absolute: time &optional;
|
||||
## A point in time relative to the last modification time at which
|
||||
## to expire the entry. New modifications will delay the expiration.
|
||||
since_last_modification: interval &optional;
|
||||
};
|
||||
|
||||
## The result of a data store query.
|
||||
type QueryResult: record {
|
||||
## Whether the query completed or not.
|
||||
status: BrokerStore::QueryStatus;
|
||||
## The result of the query. Certain queries may use a particular
|
||||
## data type (e.g. querying store size always returns a count, but
|
||||
## a lookup may return various data types).
|
||||
result: BrokerComm::Data;
|
||||
};
|
||||
|
||||
## Options to tune the SQLite storage backend.
|
||||
type SQLiteOptions: record {
|
||||
## File system path of the database.
|
||||
path: string &default = "store.sqlite";
|
||||
};
|
||||
|
||||
## Options to tune the RocksDB storage backend.
|
||||
type RocksDBOptions: record {
|
||||
## File system path of the database.
|
||||
path: string &default = "store.rocksdb";
|
||||
};
|
||||
|
||||
## Options to tune the particular storage backends.
|
||||
type BackendOptions: record {
|
||||
sqlite: SQLiteOptions &default = SQLiteOptions();
|
||||
rocksdb: RocksDBOptions &default = RocksDBOptions();
|
||||
};
|
||||
}
|
|
@ -159,5 +159,5 @@ event bro_init() &priority=5
|
|||
terminate();
|
||||
}
|
||||
|
||||
Log::create_stream(Cluster::LOG, [$columns=Info]);
|
||||
Log::create_stream(Cluster::LOG, [$columns=Info, $path="cluster"]);
|
||||
}
|
||||
|
|
|
@ -164,7 +164,7 @@ const src_names = {
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Communication::LOG, [$columns=Info]);
|
||||
Log::create_stream(Communication::LOG, [$columns=Info, $path="communication"]);
|
||||
}
|
||||
|
||||
function do_script_log_common(level: count, src: count, msg: string)
|
||||
|
|
|
@ -38,7 +38,7 @@ redef record connection += {
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(DPD::LOG, [$columns=Info]);
|
||||
Log::create_stream(DPD::LOG, [$columns=Info, $path="dpd"]);
|
||||
}
|
||||
|
||||
event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &priority=10
|
||||
|
|
|
@ -267,7 +267,7 @@ export {
|
|||
## mts: The set of MIME types, each in the form "foo/bar" (case-insensitive).
|
||||
##
|
||||
## Returns: True if the MIME types were successfully registered.
|
||||
global register_for_mime_types: function(tag: Analyzer::Tag, mts: set[string]) : bool;
|
||||
global register_for_mime_types: function(tag: Files::Tag, mts: set[string]) : bool;
|
||||
|
||||
## Registers a MIME type for an analyzer. If a future file with this type is seen,
|
||||
## the analyzer will be automatically assigned to parsing it. The function *adds*
|
||||
|
@ -278,20 +278,20 @@ export {
|
|||
## mt: The MIME type in the form "foo/bar" (case-insensitive).
|
||||
##
|
||||
## Returns: True if the MIME type was successfully registered.
|
||||
global register_for_mime_type: function(tag: Analyzer::Tag, mt: string) : bool;
|
||||
global register_for_mime_type: function(tag: Files::Tag, mt: string) : bool;
|
||||
|
||||
## Returns a set of all MIME types currently registered for a specific analyzer.
|
||||
##
|
||||
## tag: The tag of the analyzer.
|
||||
##
|
||||
## Returns: The set of MIME types.
|
||||
global registered_mime_types: function(tag: Analyzer::Tag) : set[string];
|
||||
global registered_mime_types: function(tag: Files::Tag) : set[string];
|
||||
|
||||
## Returns a table of all MIME-type-to-analyzer mappings currently registered.
|
||||
##
|
||||
## Returns: A table mapping each analyzer to the set of MIME types
|
||||
## registered for it.
|
||||
global all_registered_mime_types: function() : table[Analyzer::Tag] of set[string];
|
||||
global all_registered_mime_types: function() : table[Files::Tag] of set[string];
|
||||
|
||||
## Event that can be handled to access the Info record as it is sent on
|
||||
## to the logging framework.
|
||||
|
@ -306,14 +306,14 @@ redef record fa_file += {
|
|||
global registered_protocols: table[Analyzer::Tag] of ProtoRegistration = table();
|
||||
|
||||
# Store the MIME type to analyzer mappings.
|
||||
global mime_types: table[Analyzer::Tag] of set[string];
|
||||
global mime_type_to_analyzers: table[string] of set[Analyzer::Tag];
|
||||
global mime_types: table[Files::Tag] of set[string];
|
||||
global mime_type_to_analyzers: table[string] of set[Files::Tag];
|
||||
|
||||
global analyzer_add_callbacks: table[Files::Tag] of function(f: fa_file, args: AnalyzerArgs) = table();
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Files::LOG, [$columns=Info, $ev=log_files]);
|
||||
Log::create_stream(Files::LOG, [$columns=Info, $ev=log_files, $path="files"]);
|
||||
}
|
||||
|
||||
function set_info(f: fa_file)
|
||||
|
@ -401,7 +401,7 @@ function register_protocol(tag: Analyzer::Tag, reg: ProtoRegistration): bool
|
|||
return result;
|
||||
}
|
||||
|
||||
function register_for_mime_types(tag: Analyzer::Tag, mime_types: set[string]) : bool
|
||||
function register_for_mime_types(tag: Files::Tag, mime_types: set[string]) : bool
|
||||
{
|
||||
local rc = T;
|
||||
|
||||
|
@ -414,7 +414,7 @@ function register_for_mime_types(tag: Analyzer::Tag, mime_types: set[string]) :
|
|||
return rc;
|
||||
}
|
||||
|
||||
function register_for_mime_type(tag: Analyzer::Tag, mt: string) : bool
|
||||
function register_for_mime_type(tag: Files::Tag, mt: string) : bool
|
||||
{
|
||||
if ( tag !in mime_types )
|
||||
{
|
||||
|
@ -431,12 +431,12 @@ function register_for_mime_type(tag: Analyzer::Tag, mt: string) : bool
|
|||
return T;
|
||||
}
|
||||
|
||||
function registered_mime_types(tag: Analyzer::Tag) : set[string]
|
||||
function registered_mime_types(tag: Files::Tag) : set[string]
|
||||
{
|
||||
return tag in mime_types ? mime_types[tag] : set();
|
||||
}
|
||||
|
||||
function all_registered_mime_types(): table[Analyzer::Tag] of set[string]
|
||||
function all_registered_mime_types(): table[Files::Tag] of set[string]
|
||||
{
|
||||
return mime_types;
|
||||
}
|
||||
|
@ -451,7 +451,7 @@ function describe(f: fa_file): string
|
|||
return handler$describe(f);
|
||||
}
|
||||
|
||||
event get_file_handle(tag: Analyzer::Tag, c: connection, is_orig: bool) &priority=5
|
||||
event get_file_handle(tag: Files::Tag, c: connection, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( tag !in registered_protocols )
|
||||
return;
|
||||
|
|
|
@ -32,6 +32,8 @@ export {
|
|||
FILE_NAME,
|
||||
## Certificate SHA-1 hash.
|
||||
CERT_HASH,
|
||||
## Public key MD5 hash. (SSH server host keys are a good example.)
|
||||
PUBKEY_HASH,
|
||||
};
|
||||
|
||||
## Data about an :bro:type:`Intel::Item`.
|
||||
|
@ -174,7 +176,7 @@ global min_data_store: MinDataStore &redef;
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(LOG, [$columns=Info, $ev=log_intel]);
|
||||
Log::create_stream(LOG, [$columns=Info, $ev=log_intel, $path="intel"]);
|
||||
}
|
||||
|
||||
function find(s: Seen): bool
|
||||
|
|
|
@ -50,11 +50,17 @@ export {
|
|||
## The event receives a single same parameter, an instance of
|
||||
## type ``columns``.
|
||||
ev: any &optional;
|
||||
|
||||
## A path that will be inherited by any filters added to the
|
||||
## stream which do not already specify their own path.
|
||||
path: string &optional;
|
||||
};
|
||||
|
||||
## Builds the default path values for log filters if not otherwise
|
||||
## specified by a filter. The default implementation uses *id*
|
||||
## to derive a name.
|
||||
## to derive a name. Upon adding a filter to a stream, if neither
|
||||
## ``path`` nor ``path_func`` is explicitly set by them, then
|
||||
## this function is used as the ``path_func``.
|
||||
##
|
||||
## id: The ID associated with the log stream.
|
||||
##
|
||||
|
@ -143,7 +149,9 @@ export {
|
|||
## to compute the string dynamically. It is ok to return
|
||||
## different strings for separate calls, but be careful: it's
|
||||
## easy to flood the disk by returning a new string for each
|
||||
## connection.
|
||||
## connection. Upon adding a filter to a stream, if neither
|
||||
## ``path`` nor ``path_func`` is explicitly set by them, then
|
||||
## :bro:see:`default_path_func` is used.
|
||||
##
|
||||
## id: The ID associated with the log stream.
|
||||
##
|
||||
|
@ -379,6 +387,8 @@ export {
|
|||
global active_streams: table[ID] of Stream = table();
|
||||
}
|
||||
|
||||
global all_streams: table[ID] of Stream = table();
|
||||
|
||||
# We keep a script-level copy of all filters so that we can manipulate them.
|
||||
global filters: table[ID, string] of Filter;
|
||||
|
||||
|
@ -405,30 +415,30 @@ function default_path_func(id: ID, path: string, rec: any) : string
|
|||
|
||||
local id_str = fmt("%s", id);
|
||||
|
||||
local parts = split1(id_str, /::/);
|
||||
local parts = split_string1(id_str, /::/);
|
||||
if ( |parts| == 2 )
|
||||
{
|
||||
# Example: Notice::LOG -> "notice"
|
||||
if ( parts[2] == "LOG" )
|
||||
if ( parts[1] == "LOG" )
|
||||
{
|
||||
local module_parts = split_n(parts[1], /[^A-Z][A-Z][a-z]*/, T, 4);
|
||||
local module_parts = split_string_n(parts[0], /[^A-Z][A-Z][a-z]*/, T, 4);
|
||||
local output = "";
|
||||
if ( 1 in module_parts )
|
||||
output = module_parts[1];
|
||||
if ( 0 in module_parts )
|
||||
output = module_parts[0];
|
||||
if ( 1 in module_parts && module_parts[1] != "" )
|
||||
output = cat(output, sub_bytes(module_parts[1],1,1), "_", sub_bytes(module_parts[1], 2, |module_parts[1]|));
|
||||
if ( 2 in module_parts && module_parts[2] != "" )
|
||||
output = cat(output, sub_bytes(module_parts[2],1,1), "_", sub_bytes(module_parts[2], 2, |module_parts[2]|));
|
||||
output = cat(output, "_", module_parts[2]);
|
||||
if ( 3 in module_parts && module_parts[3] != "" )
|
||||
output = cat(output, "_", module_parts[3]);
|
||||
if ( 4 in module_parts && module_parts[4] != "" )
|
||||
output = cat(output, sub_bytes(module_parts[4],1,1), "_", sub_bytes(module_parts[4], 2, |module_parts[4]|));
|
||||
output = cat(output, sub_bytes(module_parts[3],1,1), "_", sub_bytes(module_parts[3], 2, |module_parts[3]|));
|
||||
return to_lower(output);
|
||||
}
|
||||
|
||||
# Example: Notice::POLICY_LOG -> "notice_policy"
|
||||
if ( /_LOG$/ in parts[2] )
|
||||
parts[2] = sub(parts[2], /_LOG$/, "");
|
||||
if ( /_LOG$/ in parts[1] )
|
||||
parts[1] = sub(parts[1], /_LOG$/, "");
|
||||
|
||||
return cat(to_lower(parts[1]),"_",to_lower(parts[2]));
|
||||
return cat(to_lower(parts[0]),"_",to_lower(parts[1]));
|
||||
}
|
||||
else
|
||||
return to_lower(id_str);
|
||||
|
@ -463,6 +473,7 @@ function create_stream(id: ID, stream: Stream) : bool
|
|||
return F;
|
||||
|
||||
active_streams[id] = stream;
|
||||
all_streams[id] = stream;
|
||||
|
||||
return add_default_filter(id);
|
||||
}
|
||||
|
@ -470,6 +481,7 @@ function create_stream(id: ID, stream: Stream) : bool
|
|||
function remove_stream(id: ID) : bool
|
||||
{
|
||||
delete active_streams[id];
|
||||
delete all_streams[id];
|
||||
return __remove_stream(id);
|
||||
}
|
||||
|
||||
|
@ -482,10 +494,12 @@ function disable_stream(id: ID) : bool
|
|||
|
||||
function add_filter(id: ID, filter: Filter) : bool
|
||||
{
|
||||
# This is a work-around for the fact that we can't forward-declare
|
||||
# the default_path_func and then use it as &default in the record
|
||||
# definition.
|
||||
if ( ! filter?$path_func )
|
||||
local stream = all_streams[id];
|
||||
|
||||
if ( stream?$path && ! filter?$path )
|
||||
filter$path = stream$path;
|
||||
|
||||
if ( ! filter?$path && ! filter?$path_func )
|
||||
filter$path_func = default_path_func;
|
||||
|
||||
filters[id, filter$name] = filter;
|
||||
|
|
|
@ -19,9 +19,9 @@ export {
|
|||
## the :bro:id:`NOTICE` function. The convention is to give a general
|
||||
## category along with the specific notice separating words with
|
||||
## underscores and using leading capitals on each word except for
|
||||
## abbreviations which are kept in all capitals. For example,
|
||||
## abbreviations which are kept in all capitals. For example,
|
||||
## SSH::Password_Guessing is for hosts that have crossed a threshold of
|
||||
## heuristically determined failed SSH logins.
|
||||
## failed SSH logins.
|
||||
type Type: enum {
|
||||
## Notice reporting a count of how often a notice occurred.
|
||||
Tally,
|
||||
|
@ -349,9 +349,9 @@ function log_mailing_postprocessor(info: Log::RotationInfo): bool
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Notice::LOG, [$columns=Info, $ev=log_notice]);
|
||||
Log::create_stream(Notice::LOG, [$columns=Info, $ev=log_notice, $path="notice"]);
|
||||
|
||||
Log::create_stream(Notice::ALARM_LOG, [$columns=Notice::Info]);
|
||||
Log::create_stream(Notice::ALARM_LOG, [$columns=Notice::Info, $path="notice_alarm"]);
|
||||
# If Bro is configured for mailing notices, set up mailing for alarms.
|
||||
# Make sure that this alarm log is also output as text so that it can
|
||||
# be packaged up and emailed later.
|
||||
|
|
|
@ -294,7 +294,7 @@ global current_conn: connection;
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Weird::LOG, [$columns=Info, $ev=log_weird]);
|
||||
Log::create_stream(Weird::LOG, [$columns=Info, $ev=log_weird, $path="weird"]);
|
||||
}
|
||||
|
||||
function flow_id_string(src: addr, dst: addr): string
|
||||
|
|
|
@ -159,7 +159,7 @@ event filter_change_tracking()
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(PacketFilter::LOG, [$columns=Info]);
|
||||
Log::create_stream(PacketFilter::LOG, [$columns=Info, $path="packet_filter"]);
|
||||
|
||||
# Preverify the capture and restrict filters to give more granular failure messages.
|
||||
for ( id in capture_filters )
|
||||
|
|
|
@ -45,7 +45,7 @@ export {
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Reporter::LOG, [$columns=Info]);
|
||||
Log::create_stream(Reporter::LOG, [$columns=Info, $path="reporter"]);
|
||||
}
|
||||
|
||||
event reporter_info(t: time, msg: string, location: string) &priority=-5
|
||||
|
|
|
@ -142,7 +142,7 @@ global did_sig_log: set[string] &read_expire = 1 hr;
|
|||
|
||||
event bro_init()
|
||||
{
|
||||
Log::create_stream(Signatures::LOG, [$columns=Info, $ev=log_signature]);
|
||||
Log::create_stream(Signatures::LOG, [$columns=Info, $ev=log_signature, $path="signatures"]);
|
||||
}
|
||||
|
||||
# Returns true if the given signature has already been triggered for the given
|
||||
|
@ -277,7 +277,7 @@ event signature_match(state: signature_state, msg: string, data: string)
|
|||
orig, sig_id, hcount);
|
||||
|
||||
Log::write(Signatures::LOG,
|
||||
[$note=Multiple_Sig_Responders,
|
||||
[$ts=network_time(), $note=Multiple_Sig_Responders,
|
||||
$src_addr=orig, $sig_id=sig_id, $event_msg=msg,
|
||||
$host_count=hcount, $sub_msg=horz_scan_msg]);
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ export {
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Software::LOG, [$columns=Info, $ev=log_software]);
|
||||
Log::create_stream(Software::LOG, [$columns=Info, $ev=log_software, $path="software"]);
|
||||
}
|
||||
|
||||
type Description: record {
|
||||
|
@ -133,62 +133,62 @@ function parse(unparsed_version: string): Description
|
|||
{
|
||||
# The regular expression should match the complete version number
|
||||
# and software name.
|
||||
local version_parts = split_n(unparsed_version, /\/?( [\(])?v?[0-9\-\._, ]{2,}/, T, 1);
|
||||
if ( 1 in version_parts )
|
||||
local version_parts = split_string_n(unparsed_version, /\/?( [\(])?v?[0-9\-\._, ]{2,}/, T, 1);
|
||||
if ( 0 in version_parts )
|
||||
{
|
||||
if ( /^\(/ in version_parts[1] )
|
||||
software_name = strip(sub(version_parts[1], /[\(]/, ""));
|
||||
if ( /^\(/ in version_parts[0] )
|
||||
software_name = strip(sub(version_parts[0], /[\(]/, ""));
|
||||
else
|
||||
software_name = strip(version_parts[1]);
|
||||
software_name = strip(version_parts[0]);
|
||||
}
|
||||
if ( |version_parts| >= 2 )
|
||||
{
|
||||
# Remove the name/version separator if it's left at the beginning
|
||||
# of the version number from the previous split_all.
|
||||
local sv = strip(version_parts[2]);
|
||||
local sv = strip(version_parts[1]);
|
||||
if ( /^[\/\-\._v\(]/ in sv )
|
||||
sv = strip(sub(version_parts[2], /^\(?[\/\-\._v\(]/, ""));
|
||||
local version_numbers = split_n(sv, /[\-\._,\[\(\{ ]/, F, 3);
|
||||
if ( 5 in version_numbers && version_numbers[5] != "" )
|
||||
v$addl = strip(version_numbers[5]);
|
||||
else if ( 3 in version_parts && version_parts[3] != "" &&
|
||||
version_parts[3] != ")" )
|
||||
sv = strip(sub(version_parts[1], /^\(?[\/\-\._v\(]/, ""));
|
||||
local version_numbers = split_string_n(sv, /[\-\._,\[\(\{ ]/, F, 3);
|
||||
if ( 4 in version_numbers && version_numbers[4] != "" )
|
||||
v$addl = strip(version_numbers[4]);
|
||||
else if ( 2 in version_parts && version_parts[2] != "" &&
|
||||
version_parts[2] != ")" )
|
||||
{
|
||||
if ( /^[[:blank:]]*\([a-zA-Z0-9\-\._[:blank:]]*\)/ in version_parts[3] )
|
||||
if ( /^[[:blank:]]*\([a-zA-Z0-9\-\._[:blank:]]*\)/ in version_parts[2] )
|
||||
{
|
||||
v$addl = split_n(version_parts[3], /[\(\)]/, F, 2)[2];
|
||||
v$addl = split_string_n(version_parts[2], /[\(\)]/, F, 2)[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
local vp = split_n(version_parts[3], /[\-\._,;\[\]\(\)\{\} ]/, F, 3);
|
||||
if ( |vp| >= 1 && vp[1] != "" )
|
||||
local vp = split_string_n(version_parts[2], /[\-\._,;\[\]\(\)\{\} ]/, F, 3);
|
||||
if ( |vp| >= 1 && vp[0] != "" )
|
||||
{
|
||||
v$addl = strip(vp[0]);
|
||||
}
|
||||
else if ( |vp| >= 2 && vp[1] != "" )
|
||||
{
|
||||
v$addl = strip(vp[1]);
|
||||
}
|
||||
else if ( |vp| >= 2 && vp[2] != "" )
|
||||
else if ( |vp| >= 3 && vp[2] != "" )
|
||||
{
|
||||
v$addl = strip(vp[2]);
|
||||
}
|
||||
else if ( |vp| >= 3 && vp[3] != "" )
|
||||
{
|
||||
v$addl = strip(vp[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
v$addl = strip(version_parts[3]);
|
||||
v$addl = strip(version_parts[2]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ( 4 in version_numbers && version_numbers[4] != "" )
|
||||
v$minor3 = extract_count(version_numbers[4]);
|
||||
if ( 3 in version_numbers && version_numbers[3] != "" )
|
||||
v$minor2 = extract_count(version_numbers[3]);
|
||||
v$minor3 = extract_count(version_numbers[3]);
|
||||
if ( 2 in version_numbers && version_numbers[2] != "" )
|
||||
v$minor = extract_count(version_numbers[2]);
|
||||
v$minor2 = extract_count(version_numbers[2]);
|
||||
if ( 1 in version_numbers && version_numbers[1] != "" )
|
||||
v$major = extract_count(version_numbers[1]);
|
||||
v$minor = extract_count(version_numbers[1]);
|
||||
if ( 0 in version_numbers && version_numbers[0] != "" )
|
||||
v$major = extract_count(version_numbers[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,14 +200,14 @@ function parse_mozilla(unparsed_version: string): Description
|
|||
{
|
||||
local software_name = "<unknown browser>";
|
||||
local v: Version;
|
||||
local parts: table[count] of string;
|
||||
local parts: string_vec;
|
||||
|
||||
if ( /Opera [0-9\.]*$/ in unparsed_version )
|
||||
{
|
||||
software_name = "Opera";
|
||||
parts = split_all(unparsed_version, /Opera [0-9\.]*$/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2])$version;
|
||||
parts = split_string_all(unparsed_version, /Opera [0-9\.]*$/);
|
||||
if ( 1 in parts )
|
||||
v = parse(parts[1])$version;
|
||||
}
|
||||
else if ( / MSIE |Trident\// in unparsed_version )
|
||||
{
|
||||
|
@ -222,28 +222,28 @@ function parse_mozilla(unparsed_version: string): Description
|
|||
v = [$major=11,$minor=0];
|
||||
else
|
||||
{
|
||||
parts = split_all(unparsed_version, /MSIE [0-9]{1,2}\.*[0-9]*b?[0-9]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2])$version;
|
||||
parts = split_string_all(unparsed_version, /MSIE [0-9]{1,2}\.*[0-9]*b?[0-9]*/);
|
||||
if ( 1 in parts )
|
||||
v = parse(parts[1])$version;
|
||||
}
|
||||
}
|
||||
else if ( /Version\/.*Safari\// in unparsed_version )
|
||||
{
|
||||
software_name = "Safari";
|
||||
parts = split_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
parts = split_string_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 1 in parts )
|
||||
{
|
||||
v = parse(parts[2])$version;
|
||||
v = parse(parts[1])$version;
|
||||
if ( / Mobile\/?.* Safari/ in unparsed_version )
|
||||
v$addl = "Mobile";
|
||||
}
|
||||
}
|
||||
else if ( /(Firefox|Netscape|Thunderbird)\/[0-9\.]*/ in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /(Firefox|Netscape|Thunderbird)\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
parts = split_string_all(unparsed_version, /(Firefox|Netscape|Thunderbird)\/[0-9\.]*/);
|
||||
if ( 1 in parts )
|
||||
{
|
||||
local tmp_s = parse(parts[2]);
|
||||
local tmp_s = parse(parts[1]);
|
||||
software_name = tmp_s$name;
|
||||
v = tmp_s$version;
|
||||
}
|
||||
|
@ -251,48 +251,48 @@ function parse_mozilla(unparsed_version: string): Description
|
|||
else if ( /Chrome\/.*Safari\// in unparsed_version )
|
||||
{
|
||||
software_name = "Chrome";
|
||||
parts = split_all(unparsed_version, /Chrome\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2])$version;
|
||||
parts = split_string_all(unparsed_version, /Chrome\/[0-9\.]*/);
|
||||
if ( 1 in parts )
|
||||
v = parse(parts[1])$version;
|
||||
}
|
||||
else if ( /^Opera\// in unparsed_version )
|
||||
{
|
||||
if ( /Opera M(ini|obi)\// in unparsed_version )
|
||||
{
|
||||
parts = split_all(unparsed_version, /Opera M(ini|obi)/);
|
||||
if ( 2 in parts )
|
||||
software_name = parts[2];
|
||||
parts = split_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2])$version;
|
||||
parts = split_string_all(unparsed_version, /Opera M(ini|obi)/);
|
||||
if ( 1 in parts )
|
||||
software_name = parts[1];
|
||||
parts = split_string_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 1 in parts )
|
||||
v = parse(parts[1])$version;
|
||||
else
|
||||
{
|
||||
parts = split_all(unparsed_version, /Opera Mini\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2])$version;
|
||||
parts = split_string_all(unparsed_version, /Opera Mini\/[0-9\.]*/);
|
||||
if ( 1 in parts )
|
||||
v = parse(parts[1])$version;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
software_name = "Opera";
|
||||
parts = split_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2])$version;
|
||||
parts = split_string_all(unparsed_version, /Version\/[0-9\.]*/);
|
||||
if ( 1 in parts )
|
||||
v = parse(parts[1])$version;
|
||||
}
|
||||
}
|
||||
else if ( /AppleWebKit\/[0-9\.]*/ in unparsed_version )
|
||||
{
|
||||
software_name = "Unspecified WebKit";
|
||||
parts = split_all(unparsed_version, /AppleWebKit\/[0-9\.]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2])$version;
|
||||
parts = split_string_all(unparsed_version, /AppleWebKit\/[0-9\.]*/);
|
||||
if ( 1 in parts )
|
||||
v = parse(parts[1])$version;
|
||||
}
|
||||
else if ( / Java\/[0-9]\./ in unparsed_version )
|
||||
{
|
||||
software_name = "Java";
|
||||
parts = split_all(unparsed_version, /Java\/[0-9\._]*/);
|
||||
if ( 2 in parts )
|
||||
v = parse(parts[2])$version;
|
||||
parts = split_string_all(unparsed_version, /Java\/[0-9\._]*/);
|
||||
if ( 1 in parts )
|
||||
v = parse(parts[1])$version;
|
||||
}
|
||||
|
||||
return [$version=v, $unparsed_version=unparsed_version, $name=software_name];
|
||||
|
|
|
@ -89,7 +89,7 @@ redef likely_server_ports += { ayiya_ports, teredo_ports, gtpv1_ports };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Tunnel::LOG, [$columns=Info]);
|
||||
Log::create_stream(Tunnel::LOG, [$columns=Info, $path="tunnel"]);
|
||||
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_AYIYA, ayiya_ports);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_TEREDO, teredo_ports);
|
||||
|
|
|
@ -440,6 +440,7 @@ type NetStats: record {
|
|||
## packet capture system, this value may not be available and will then
|
||||
## be always set to zero.
|
||||
pkts_link: count &default=0;
|
||||
bytes_recvd: count &default=0; ##< Bytes received by Bro.
|
||||
};
|
||||
|
||||
## Statistics about Bro's resource consumption.
|
||||
|
@ -928,7 +929,7 @@ const tcp_storm_interarrival_thresh = 1 sec &redef;
|
|||
## seeing our peer's ACKs. Set to zero to turn off this determination.
|
||||
##
|
||||
## .. bro:see:: tcp_max_above_hole_without_any_acks tcp_excessive_data_without_further_acks
|
||||
const tcp_max_initial_window = 4096 &redef;
|
||||
const tcp_max_initial_window = 16384 &redef;
|
||||
|
||||
## If we're not seeing our peer's ACKs, the maximum volume of data above a
|
||||
## sequence hole that we'll tolerate before assuming that there's been a packet
|
||||
|
@ -936,7 +937,7 @@ const tcp_max_initial_window = 4096 &redef;
|
|||
## don't ever give up.
|
||||
##
|
||||
## .. bro:see:: tcp_max_initial_window tcp_excessive_data_without_further_acks
|
||||
const tcp_max_above_hole_without_any_acks = 4096 &redef;
|
||||
const tcp_max_above_hole_without_any_acks = 16384 &redef;
|
||||
|
||||
## If we've seen this much data without any of it being acked, we give up
|
||||
## on that connection to avoid memory exhaustion due to buffering all that
|
||||
|
@ -2215,6 +2216,41 @@ export {
|
|||
const heartbeat_interval = 1.0 secs &redef;
|
||||
}
|
||||
|
||||
module SSH;
|
||||
|
||||
export {
|
||||
## The client and server each have some preferences for the algorithms used
|
||||
## in each direction.
|
||||
type Algorithm_Prefs: record {
|
||||
## The algorithm preferences for client to server communication
|
||||
client_to_server: vector of string &optional;
|
||||
## The algorithm preferences for server to client communication
|
||||
server_to_client: vector of string &optional;
|
||||
};
|
||||
|
||||
## This record lists the preferences of an SSH endpoint for
|
||||
## algorithm selection. During the initial :abbr:`SSH (Secure Shell)`
|
||||
## key exchange, each endpoint lists the algorithms
|
||||
## that it supports, in order of preference. See
|
||||
## :rfc:`4253#section-7.1` for details.
|
||||
type Capabilities: record {
|
||||
## Key exchange algorithms
|
||||
kex_algorithms: string_vec;
|
||||
## The algorithms supported for the server host key
|
||||
server_host_key_algorithms: string_vec;
|
||||
## Symmetric encryption algorithm preferences
|
||||
encryption_algorithms: Algorithm_Prefs;
|
||||
## Symmetric MAC algorithm preferences
|
||||
mac_algorithms: Algorithm_Prefs;
|
||||
## Compression algorithm preferences
|
||||
compression_algorithms: Algorithm_Prefs;
|
||||
## Language preferences
|
||||
languages: Algorithm_Prefs &optional;
|
||||
## Are these the capabilities of the server?
|
||||
is_server: bool;
|
||||
};
|
||||
}
|
||||
|
||||
module GLOBAL;
|
||||
|
||||
## An NTP message.
|
||||
|
@ -2849,19 +2885,20 @@ export {
|
|||
module X509;
|
||||
export {
|
||||
type Certificate: record {
|
||||
version: count; ##< Version number.
|
||||
serial: string; ##< Serial number.
|
||||
subject: string; ##< Subject.
|
||||
issuer: string; ##< Issuer.
|
||||
not_valid_before: time; ##< Timestamp before when certificate is not valid.
|
||||
not_valid_after: time; ##< Timestamp after when certificate is not valid.
|
||||
key_alg: string; ##< Name of the key algorithm
|
||||
sig_alg: string; ##< Name of the signature algorithm
|
||||
key_type: string &optional; ##< Key type, if key parseable by openssl (either rsa, dsa or ec)
|
||||
key_length: count &optional; ##< Key length in bits
|
||||
exponent: string &optional; ##< Exponent, if RSA-certificate
|
||||
curve: string &optional; ##< Curve, if EC-certificate
|
||||
} &log;
|
||||
version: count &log; ##< Version number.
|
||||
serial: string &log; ##< Serial number.
|
||||
subject: string &log; ##< Subject.
|
||||
issuer: string &log; ##< Issuer.
|
||||
cn: string &optional; ##< Last (most specific) common name.
|
||||
not_valid_before: time &log; ##< Timestamp before when certificate is not valid.
|
||||
not_valid_after: time &log; ##< Timestamp after when certificate is not valid.
|
||||
key_alg: string &log; ##< Name of the key algorithm
|
||||
sig_alg: string &log; ##< Name of the signature algorithm
|
||||
key_type: string &optional &log; ##< Key type, if key parseable by openssl (either rsa, dsa or ec)
|
||||
key_length: count &optional &log; ##< Key length in bits
|
||||
exponent: string &optional &log; ##< Exponent, if RSA-certificate
|
||||
curve: string &optional &log; ##< Curve, if EC-certificate
|
||||
};
|
||||
|
||||
type Extension: record {
|
||||
name: string; ##< Long name of extension. oid if name not known
|
||||
|
@ -2922,7 +2959,44 @@ export {
|
|||
attributes : RADIUS::Attributes &optional;
|
||||
};
|
||||
}
|
||||
module GLOBAL;
|
||||
|
||||
module RDP;
|
||||
export {
|
||||
type RDP::EarlyCapabilityFlags: record {
|
||||
support_err_info_pdu: bool;
|
||||
want_32bpp_session: bool;
|
||||
support_statusinfo_pdu: bool;
|
||||
strong_asymmetric_keys: bool;
|
||||
support_monitor_layout_pdu: bool;
|
||||
support_netchar_autodetect: bool;
|
||||
support_dynvc_gfx_protocol: bool;
|
||||
support_dynamic_time_zone: bool;
|
||||
support_heartbeat_pdu: bool;
|
||||
};
|
||||
|
||||
type RDP::ClientCoreData: record {
|
||||
version_major: count;
|
||||
version_minor: count;
|
||||
desktop_width: count;
|
||||
desktop_height: count;
|
||||
color_depth: count;
|
||||
sas_sequence: count;
|
||||
keyboard_layout: count;
|
||||
client_build: count;
|
||||
client_name: string;
|
||||
keyboard_type: count;
|
||||
keyboard_sub: count;
|
||||
keyboard_function_key: count;
|
||||
ime_file_name: string;
|
||||
post_beta2_color_depth: count &optional;
|
||||
client_product_id: string &optional;
|
||||
serial_number: count &optional;
|
||||
high_color_depth: count &optional;
|
||||
supported_color_depths: count &optional;
|
||||
ec_flags: RDP::EarlyCapabilityFlags &optional;
|
||||
dig_product_id: string &optional;
|
||||
};
|
||||
}
|
||||
|
||||
@load base/bif/plugins/Bro_SNMP.types.bif
|
||||
|
||||
|
@ -3433,6 +3507,7 @@ const bits_per_uid: count = 96 &redef;
|
|||
|
||||
# Load these frameworks here because they use fairly deep integration with
|
||||
# BiFs and script-land defined types.
|
||||
@load base/frameworks/broker
|
||||
@load base/frameworks/logging
|
||||
@load base/frameworks/input
|
||||
@load base/frameworks/analyzer
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
@load base/protocols/mysql
|
||||
@load base/protocols/pop3
|
||||
@load base/protocols/radius
|
||||
@load base/protocols/rdp
|
||||
@load base/protocols/snmp
|
||||
@load base/protocols/smtp
|
||||
@load base/protocols/socks
|
||||
|
|
|
@ -50,7 +50,7 @@ event ChecksumOffloading::check()
|
|||
bad_checksum_msg += "UDP";
|
||||
}
|
||||
|
||||
local message = fmt("Your %s invalid %s checksums, most likely from NIC checksum offloading.", packet_src, bad_checksum_msg);
|
||||
local message = fmt("Your %s invalid %s checksums, most likely from NIC checksum offloading. By default, packets with invalid checksums are discarded by Bro unless using the -C command-line option or toggling the 'ignore_checksums' variable. Alternatively, disable checksum offloading by the network adapter to ensure Bro analyzes the actual checksums that are transmitted.", packet_src, bad_checksum_msg);
|
||||
Reporter::warning(message);
|
||||
done = T;
|
||||
}
|
||||
|
|
|
@ -62,6 +62,12 @@ export {
|
|||
## field will be left empty at all times.
|
||||
local_orig: bool &log &optional;
|
||||
|
||||
## If the connection is responded to locally, this value will be T.
|
||||
## If it was responded to remotely it will be F. In the case that
|
||||
## the :bro:id:`Site::local_nets` variable is undefined, this
|
||||
## field will be left empty at all times.
|
||||
local_resp: bool &log &optional;
|
||||
|
||||
## Indicates the number of bytes missed in content gaps, which
|
||||
## is representative of packet loss. A value other than zero
|
||||
## will normally cause protocol analysis to fail but some
|
||||
|
@ -121,7 +127,7 @@ redef record connection += {
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Conn::LOG, [$columns=Info, $ev=log_conn]);
|
||||
Log::create_stream(Conn::LOG, [$columns=Info, $ev=log_conn, $path="conn"]);
|
||||
}
|
||||
|
||||
function conn_state(c: connection, trans: transport_proto): string
|
||||
|
@ -201,7 +207,10 @@ function set_conn(c: connection, eoc: bool)
|
|||
add c$conn$tunnel_parents[c$tunnel[|c$tunnel|-1]$uid];
|
||||
c$conn$proto=get_port_transport_proto(c$id$resp_p);
|
||||
if( |Site::local_nets| > 0 )
|
||||
{
|
||||
c$conn$local_orig=Site::is_local_addr(c$id$orig_h);
|
||||
c$conn$local_resp=Site::is_local_addr(c$id$resp_h);
|
||||
}
|
||||
|
||||
if ( eoc )
|
||||
{
|
||||
|
|
|
@ -49,7 +49,7 @@ redef likely_server_ports += { 67/udp };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(DHCP::LOG, [$columns=Info, $ev=log_dhcp]);
|
||||
Log::create_stream(DHCP::LOG, [$columns=Info, $ev=log_dhcp, $path="dhcp"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_DHCP, ports);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ export {
|
|||
|
||||
function reverse_ip(ip: addr): addr
|
||||
{
|
||||
local octets = split(cat(ip), /\./);
|
||||
return to_addr(cat(octets[4], ".", octets[3], ".", octets[2], ".", octets[1]));
|
||||
local octets = split_string(cat(ip), /\./);
|
||||
return to_addr(cat(octets[3], ".", octets[2], ".", octets[1], ".", octets[0]));
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ redef likely_server_ports += { ports };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(DNP3::LOG, [$columns=Info, $ev=log_dnp3]);
|
||||
Log::create_stream(DNP3::LOG, [$columns=Info, $ev=log_dnp3, $path="dnp3"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_DNP3_TCP, ports);
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ redef likely_server_ports += { ports };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(DNS::LOG, [$columns=Info, $ev=log_dns]);
|
||||
Log::create_stream(DNS::LOG, [$columns=Info, $ev=log_dns, $path="dns"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_DNS, ports);
|
||||
}
|
||||
|
||||
|
@ -305,6 +305,9 @@ hook DNS::do_reply(c: connection, msg: dns_msg, ans: dns_answer, reply: string)
|
|||
|
||||
if ( ans$answer_type == DNS_ANS )
|
||||
{
|
||||
if ( ! c$dns?$query )
|
||||
c$dns$query = ans$query;
|
||||
|
||||
c$dns$AA = msg$AA;
|
||||
c$dns$RA = msg$RA;
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ redef likely_server_ports += { ports };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(FTP::LOG, [$columns=Info, $ev=log_ftp]);
|
||||
Log::create_stream(FTP::LOG, [$columns=Info, $ev=log_ftp, $path="ftp"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_FTP, ports);
|
||||
}
|
||||
|
||||
|
@ -274,7 +274,7 @@ event file_transferred(c: connection, prefix: string, descr: string,
|
|||
if ( [id$resp_h, id$resp_p] in ftp_data_expected )
|
||||
{
|
||||
local s = ftp_data_expected[id$resp_h, id$resp_p];
|
||||
s$mime_type = split1(mime_type, /;/)[1];
|
||||
s$mime_type = split_string1(mime_type, /;/)[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -135,7 +135,7 @@ redef likely_server_ports += { ports };
|
|||
# Initialize the HTTP logging stream and ports.
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(HTTP::LOG, [$columns=Info, $ev=log_http]);
|
||||
Log::create_stream(HTTP::LOG, [$columns=Info, $ev=log_http, $path="http"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_HTTP, ports);
|
||||
}
|
||||
|
||||
|
@ -242,7 +242,7 @@ event http_header(c: connection, is_orig: bool, name: string, value: string) &pr
|
|||
|
||||
else if ( name == "HOST" )
|
||||
# The split is done to remove the occasional port value that shows up here.
|
||||
c$http$host = split1(value, /:/)[1];
|
||||
c$http$host = split_string1(value, /:/)[0];
|
||||
|
||||
else if ( name == "RANGE" )
|
||||
c$http$range_request = T;
|
||||
|
@ -262,12 +262,12 @@ event http_header(c: connection, is_orig: bool, name: string, value: string) &pr
|
|||
if ( /^[bB][aA][sS][iI][cC] / in value )
|
||||
{
|
||||
local userpass = decode_base64(sub(value, /[bB][aA][sS][iI][cC][[:blank:]]/, ""));
|
||||
local up = split(userpass, /:/);
|
||||
local up = split_string(userpass, /:/);
|
||||
if ( |up| >= 2 )
|
||||
{
|
||||
c$http$username = up[1];
|
||||
c$http$username = up[0];
|
||||
if ( c$http$capture_password )
|
||||
c$http$password = up[2];
|
||||
c$http$password = up[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -42,12 +42,12 @@ function extract_keys(data: string, kv_splitter: pattern): string_vec
|
|||
{
|
||||
local key_vec: vector of string = vector();
|
||||
|
||||
local parts = split(data, kv_splitter);
|
||||
local parts = split_string(data, kv_splitter);
|
||||
for ( part_index in parts )
|
||||
{
|
||||
local key_val = split1(parts[part_index], /=/);
|
||||
if ( 1 in key_val )
|
||||
key_vec[|key_vec|] = key_val[1];
|
||||
local key_val = split_string1(parts[part_index], /=/);
|
||||
if ( 0 in key_val )
|
||||
key_vec[|key_vec|] = key_val[0];
|
||||
}
|
||||
return key_vec;
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ redef likely_server_ports += { ports };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(IRC::LOG, [$columns=Info, $ev=irc_log]);
|
||||
Log::create_stream(IRC::LOG, [$columns=Info, $ev=irc_log, $path="irc"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_IRC, ports);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ redef likely_server_ports += { ports };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Modbus::LOG, [$columns=Info, $ev=log_modbus]);
|
||||
Log::create_stream(Modbus::LOG, [$columns=Info, $ev=log_modbus, $path="modbus"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_MODBUS, ports);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ const ports = { 1434/tcp, 3306/tcp };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(mysql::LOG, [$columns=Info, $ev=log_mysql]);
|
||||
Log::create_stream(mysql::LOG, [$columns=Info, $ev=log_mysql, $path="mysql"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_MYSQL, ports);
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ const ports = { 1812/udp };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(RADIUS::LOG, [$columns=Info, $ev=log_radius]);
|
||||
Log::create_stream(RADIUS::LOG, [$columns=Info, $ev=log_radius, $path="radius"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_RADIUS, ports);
|
||||
}
|
||||
|
||||
|
|
3
scripts/base/protocols/rdp/__load__.bro
Normal file
3
scripts/base/protocols/rdp/__load__.bro
Normal file
|
@ -0,0 +1,3 @@
|
|||
@load ./consts
|
||||
@load ./main
|
||||
@load-sigs ./dpd.sig
|
323
scripts/base/protocols/rdp/consts.bro
Normal file
323
scripts/base/protocols/rdp/consts.bro
Normal file
|
@ -0,0 +1,323 @@
|
|||
module RDP;
|
||||
|
||||
export {
|
||||
# http://www.c-amie.co.uk/technical/mstsc-versions/
|
||||
const builds = {
|
||||
[0419] = "RDP 4.0",
|
||||
[2195] = "RDP 5.0",
|
||||
[2221] = "RDP 5.0",
|
||||
[2600] = "RDP 5.1",
|
||||
[3790] = "RDP 5.2",
|
||||
[6000] = "RDP 6.0",
|
||||
[6001] = "RDP 6.1",
|
||||
[6002] = "RDP 6.2",
|
||||
[7600] = "RDP 7.0",
|
||||
[7601] = "RDP 7.1",
|
||||
[9200] = "RDP 8.0",
|
||||
[9600] = "RDP 8.1",
|
||||
[25189] = "RDP 8.0 (Mac)",
|
||||
[25282] = "RDP 8.0 (Mac)"
|
||||
} &default = function(n: count): string { return fmt("client_build-%d", n); };
|
||||
|
||||
const security_protocols = {
|
||||
[0x00] = "RDP",
|
||||
[0x01] = "SSL",
|
||||
[0x02] = "HYBRID",
|
||||
[0x08] = "HYBRID_EX"
|
||||
} &default = function(n: count): string { return fmt("security_protocol-%d", n); };
|
||||
|
||||
const failure_codes = {
|
||||
[0x01] = "SSL_REQUIRED_BY_SERVER",
|
||||
[0x02] = "SSL_NOT_ALLOWED_BY_SERVER",
|
||||
[0x03] = "SSL_CERT_NOT_ON_SERVER",
|
||||
[0x04] = "INCONSISTENT_FLAGS",
|
||||
[0x05] = "HYBRID_REQUIRED_BY_SERVER",
|
||||
[0x06] = "SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER"
|
||||
} &default = function(n: count): string { return fmt("failure_code-%d", n); };
|
||||
|
||||
const cert_types = {
|
||||
[1] = "RSA",
|
||||
[2] = "X.509"
|
||||
} &default = function(n: count): string { return fmt("cert_type-%d", n); };
|
||||
|
||||
const encryption_methods = {
|
||||
[0] = "None",
|
||||
[1] = "40bit",
|
||||
[2] = "128bit",
|
||||
[8] = "56bit",
|
||||
[10] = "FIPS"
|
||||
} &default = function(n: count): string { return fmt("encryption_method-%d", n); };
|
||||
|
||||
const encryption_levels = {
|
||||
[0] = "None",
|
||||
[1] = "Low",
|
||||
[2] = "Client compatible",
|
||||
[3] = "High",
|
||||
[4] = "FIPS"
|
||||
} &default = function(n: count): string { return fmt("encryption_level-%d", n); };
|
||||
|
||||
const high_color_depths = {
|
||||
[0x0004] = "4bit",
|
||||
[0x0008] = "8bit",
|
||||
[0x000F] = "15bit",
|
||||
[0x0010] = "16bit",
|
||||
[0x0018] = "24bit"
|
||||
} &default = function(n: count): string { return fmt("high_color_depth-%d", n); };
|
||||
|
||||
const color_depths = {
|
||||
[0x0001] = "24bit",
|
||||
[0x0002] = "16bit",
|
||||
[0x0004] = "15bit",
|
||||
[0x0008] = "32bit"
|
||||
} &default = function(n: count): string { return fmt("color_depth-%d", n); };
|
||||
|
||||
const results = {
|
||||
[0] = "Success",
|
||||
[1] = "User rejected",
|
||||
[2] = "Resources not available",
|
||||
[3] = "Rejected for symmetry breaking",
|
||||
[4] = "Locked conference",
|
||||
} &default = function(n: count): string { return fmt("result-%d", n); };
|
||||
|
||||
# http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx
|
||||
const languages = {
|
||||
[1078] = "Afrikaans - South Africa",
|
||||
[1052] = "Albanian - Albania",
|
||||
[1156] = "Alsatian",
|
||||
[1118] = "Amharic - Ethiopia",
|
||||
[1025] = "Arabic - Saudi Arabia",
|
||||
[5121] = "Arabic - Algeria",
|
||||
[15361] = "Arabic - Bahrain",
|
||||
[3073] = "Arabic - Egypt",
|
||||
[2049] = "Arabic - Iraq",
|
||||
[11265] = "Arabic - Jordan",
|
||||
[13313] = "Arabic - Kuwait",
|
||||
[12289] = "Arabic - Lebanon",
|
||||
[4097] = "Arabic - Libya",
|
||||
[6145] = "Arabic - Morocco",
|
||||
[8193] = "Arabic - Oman",
|
||||
[16385] = "Arabic - Qatar",
|
||||
[10241] = "Arabic - Syria",
|
||||
[7169] = "Arabic - Tunisia",
|
||||
[14337] = "Arabic - U.A.E.",
|
||||
[9217] = "Arabic - Yemen",
|
||||
[1067] = "Armenian - Armenia",
|
||||
[1101] = "Assamese",
|
||||
[2092] = "Azeri (Cyrillic)",
|
||||
[1068] = "Azeri (Latin)",
|
||||
[1133] = "Bashkir",
|
||||
[1069] = "Basque",
|
||||
[1059] = "Belarusian",
|
||||
[1093] = "Bengali (India)",
|
||||
[2117] = "Bengali (Bangladesh)",
|
||||
[5146] = "Bosnian (Bosnia/Herzegovina)",
|
||||
[1150] = "Breton",
|
||||
[1026] = "Bulgarian",
|
||||
[1109] = "Burmese",
|
||||
[1027] = "Catalan",
|
||||
[1116] = "Cherokee - United States",
|
||||
[2052] = "Chinese - People's Republic of China",
|
||||
[4100] = "Chinese - Singapore",
|
||||
[1028] = "Chinese - Taiwan",
|
||||
[3076] = "Chinese - Hong Kong SAR",
|
||||
[5124] = "Chinese - Macao SAR",
|
||||
[1155] = "Corsican",
|
||||
[1050] = "Croatian",
|
||||
[4122] = "Croatian (Bosnia/Herzegovina)",
|
||||
[1029] = "Czech",
|
||||
[1030] = "Danish",
|
||||
[1164] = "Dari",
|
||||
[1125] = "Divehi",
|
||||
[1043] = "Dutch - Netherlands",
|
||||
[2067] = "Dutch - Belgium",
|
||||
[1126] = "Edo",
|
||||
[1033] = "English - United States",
|
||||
[2057] = "English - United Kingdom",
|
||||
[3081] = "English - Australia",
|
||||
[10249] = "English - Belize",
|
||||
[4105] = "English - Canada",
|
||||
[9225] = "English - Caribbean",
|
||||
[15369] = "English - Hong Kong SAR",
|
||||
[16393] = "English - India",
|
||||
[14345] = "English - Indonesia",
|
||||
[6153] = "English - Ireland",
|
||||
[8201] = "English - Jamaica",
|
||||
[17417] = "English - Malaysia",
|
||||
[5129] = "English - New Zealand",
|
||||
[13321] = "English - Philippines",
|
||||
[18441] = "English - Singapore",
|
||||
[7177] = "English - South Africa",
|
||||
[11273] = "English - Trinidad",
|
||||
[12297] = "English - Zimbabwe",
|
||||
[1061] = "Estonian",
|
||||
[1080] = "Faroese",
|
||||
[1065] = "Farsi",
|
||||
[1124] = "Filipino",
|
||||
[1035] = "Finnish",
|
||||
[1036] = "French - France",
|
||||
[2060] = "French - Belgium",
|
||||
[11276] = "French - Cameroon",
|
||||
[3084] = "French - Canada",
|
||||
[9228] = "French - Democratic Rep. of Congo",
|
||||
[12300] = "French - Cote d'Ivoire",
|
||||
[15372] = "French - Haiti",
|
||||
[5132] = "French - Luxembourg",
|
||||
[13324] = "French - Mali",
|
||||
[6156] = "French - Monaco",
|
||||
[14348] = "French - Morocco",
|
||||
[58380] = "French - North Africa",
|
||||
[8204] = "French - Reunion",
|
||||
[10252] = "French - Senegal",
|
||||
[4108] = "French - Switzerland",
|
||||
[7180] = "French - West Indies",
|
||||
[1122] = "French - West Indies",
|
||||
[1127] = "Fulfulde - Nigeria",
|
||||
[1071] = "FYRO Macedonian",
|
||||
[1110] = "Galician",
|
||||
[1079] = "Georgian",
|
||||
[1031] = "German - Germany",
|
||||
[3079] = "German - Austria",
|
||||
[5127] = "German - Liechtenstein",
|
||||
[4103] = "German - Luxembourg",
|
||||
[2055] = "German - Switzerland",
|
||||
[1032] = "Greek",
|
||||
[1135] = "Greenlandic",
|
||||
[1140] = "Guarani - Paraguay",
|
||||
[1095] = "Gujarati",
|
||||
[1128] = "Hausa - Nigeria",
|
||||
[1141] = "Hawaiian - United States",
|
||||
[1037] = "Hebrew",
|
||||
[1081] = "Hindi",
|
||||
[1038] = "Hungarian",
|
||||
[1129] = "Ibibio - Nigeria",
|
||||
[1039] = "Icelandic",
|
||||
[1136] = "Igbo - Nigeria",
|
||||
[1057] = "Indonesian",
|
||||
[1117] = "Inuktitut",
|
||||
[2108] = "Irish",
|
||||
[1040] = "Italian - Italy",
|
||||
[2064] = "Italian - Switzerland",
|
||||
[1041] = "Japanese",
|
||||
[1158] = "K'iche",
|
||||
[1099] = "Kannada",
|
||||
[1137] = "Kanuri - Nigeria",
|
||||
[2144] = "Kashmiri",
|
||||
[1120] = "Kashmiri (Arabic)",
|
||||
[1087] = "Kazakh",
|
||||
[1107] = "Khmer",
|
||||
[1159] = "Kinyarwanda",
|
||||
[1111] = "Konkani",
|
||||
[1042] = "Korean",
|
||||
[1088] = "Kyrgyz (Cyrillic)",
|
||||
[1108] = "Lao",
|
||||
[1142] = "Latin",
|
||||
[1062] = "Latvian",
|
||||
[1063] = "Lithuanian",
|
||||
[1134] = "Luxembourgish",
|
||||
[1086] = "Malay - Malaysia",
|
||||
[2110] = "Malay - Brunei Darussalam",
|
||||
[1100] = "Malayalam",
|
||||
[1082] = "Maltese",
|
||||
[1112] = "Manipuri",
|
||||
[1153] = "Maori - New Zealand",
|
||||
[1146] = "Mapudungun",
|
||||
[1102] = "Marathi",
|
||||
[1148] = "Mohawk",
|
||||
[1104] = "Mongolian (Cyrillic)",
|
||||
[2128] = "Mongolian (Mongolian)",
|
||||
[1121] = "Nepali",
|
||||
[2145] = "Nepali - India",
|
||||
[1044] = "Norwegian (Bokmål)",
|
||||
[2068] = "Norwegian (Nynorsk)",
|
||||
[1154] = "Occitan",
|
||||
[1096] = "Oriya",
|
||||
[1138] = "Oromo",
|
||||
[1145] = "Papiamentu",
|
||||
[1123] = "Pashto",
|
||||
[1045] = "Polish",
|
||||
[1046] = "Portuguese - Brazil",
|
||||
[2070] = "Portuguese - Portugal",
|
||||
[1094] = "Punjabi",
|
||||
[2118] = "Punjabi (Pakistan)",
|
||||
[1131] = "Quecha - Bolivia",
|
||||
[2155] = "Quecha - Ecuador",
|
||||
[3179] = "Quecha - Peru CB",
|
||||
[1047] = "Rhaeto-Romanic",
|
||||
[1048] = "Romanian",
|
||||
[2072] = "Romanian - Moldava",
|
||||
[1049] = "Russian",
|
||||
[2073] = "Russian - Moldava",
|
||||
[1083] = "Sami (Lappish)",
|
||||
[1103] = "Sanskrit",
|
||||
[1084] = "Scottish Gaelic",
|
||||
[1132] = "Sepedi",
|
||||
[3098] = "Serbian (Cyrillic)",
|
||||
[2074] = "Serbian (Latin)",
|
||||
[1113] = "Sindhi - India",
|
||||
[2137] = "Sindhi - Pakistan",
|
||||
[1115] = "Sinhalese - Sri Lanka",
|
||||
[1051] = "Slovak",
|
||||
[1060] = "Slovenian",
|
||||
[1143] = "Somali",
|
||||
[1070] = "Sorbian",
|
||||
[3082] = "Spanish - Spain (Modern Sort)",
|
||||
[1034] = "Spanish - Spain (Traditional Sort)",
|
||||
[11274] = "Spanish - Argentina",
|
||||
[16394] = "Spanish - Bolivia",
|
||||
[13322] = "Spanish - Chile",
|
||||
[9226] = "Spanish - Colombia",
|
||||
[5130] = "Spanish - Costa Rica",
|
||||
[7178] = "Spanish - Dominican Republic",
|
||||
[12298] = "Spanish - Ecuador",
|
||||
[17418] = "Spanish - El Salvador",
|
||||
[4106] = "Spanish - Guatemala",
|
||||
[18442] = "Spanish - Honduras",
|
||||
[22538] = "Spanish - Latin America",
|
||||
[2058] = "Spanish - Mexico",
|
||||
[19466] = "Spanish - Nicaragua",
|
||||
[6154] = "Spanish - Panama",
|
||||
[15370] = "Spanish - Paraguay",
|
||||
[10250] = "Spanish - Peru",
|
||||
[20490] = "Spanish - Puerto Rico",
|
||||
[21514] = "Spanish - United States",
|
||||
[14346] = "Spanish - Uruguay",
|
||||
[8202] = "Spanish - Venezuela",
|
||||
[1072] = "Sutu",
|
||||
[1089] = "Swahili",
|
||||
[1053] = "Swedish",
|
||||
[2077] = "Swedish - Finland",
|
||||
[1114] = "Syriac",
|
||||
[1064] = "Tajik",
|
||||
[1119] = "Tamazight (Arabic)",
|
||||
[2143] = "Tamazight (Latin)",
|
||||
[1097] = "Tamil",
|
||||
[1092] = "Tatar",
|
||||
[1098] = "Telugu",
|
||||
[1054] = "Thai",
|
||||
[2129] = "Tibetan - Bhutan",
|
||||
[1105] = "Tibetan - People's Republic of China",
|
||||
[2163] = "Tigrigna - Eritrea",
|
||||
[1139] = "Tigrigna - Ethiopia",
|
||||
[1073] = "Tsonga",
|
||||
[1074] = "Tswana",
|
||||
[1055] = "Turkish",
|
||||
[1090] = "Turkmen",
|
||||
[1152] = "Uighur - China",
|
||||
[1058] = "Ukrainian",
|
||||
[1056] = "Urdu",
|
||||
[2080] = "Urdu - India",
|
||||
[2115] = "Uzbek (Cyrillic)",
|
||||
[1091] = "Uzbek (Latin)",
|
||||
[1075] = "Venda",
|
||||
[1066] = "Vietnamese",
|
||||
[1106] = "Welsh",
|
||||
[1160] = "Wolof",
|
||||
[1076] = "Xhosa",
|
||||
[1157] = "Yakut",
|
||||
[1144] = "Yi",
|
||||
[1085] = "Yiddish",
|
||||
[1130] = "Yoruba",
|
||||
[1077] = "Zulu",
|
||||
[1279] = "HID (Human Interface Device)",
|
||||
} &default = function(n: count): string { return fmt("keyboard-%d", n); };
|
||||
}
|
12
scripts/base/protocols/rdp/dpd.sig
Normal file
12
scripts/base/protocols/rdp/dpd.sig
Normal file
|
@ -0,0 +1,12 @@
|
|||
signature dpd_rdp_client {
|
||||
ip-proto == tcp
|
||||
# Client request
|
||||
payload /.*(Cookie: mstshash\=|Duca.*(rdpdr|rdpsnd|drdynvc|cliprdr))/
|
||||
requires-reverse-signature dpd_rdp_server
|
||||
enable "rdp"
|
||||
}
|
||||
|
||||
signature dpd_rdp_server {
|
||||
ip-proto == tcp
|
||||
payload /(.{5}\xd0|.*McDn)/
|
||||
}
|
269
scripts/base/protocols/rdp/main.bro
Normal file
269
scripts/base/protocols/rdp/main.bro
Normal file
|
@ -0,0 +1,269 @@
|
|||
##! Implements base functionality for RDP analysis. Generates the rdp.log file.
|
||||
|
||||
@load ./consts
|
||||
|
||||
module RDP;
|
||||
|
||||
export {
|
||||
redef enum Log::ID += { 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;
|
||||
## Cookie value used by the client machine.
|
||||
## This is typically a username.
|
||||
cookie: string &log &optional;
|
||||
## Status result for the connection. It's a mix between
|
||||
## RDP negotation failure messages and GCC server create
|
||||
## response messages.
|
||||
result: string &log &optional;
|
||||
## Security protocol chosen by the server.
|
||||
security_protocol: string &log &optional;
|
||||
|
||||
## Keyboard layout (language) of the client machine.
|
||||
keyboard_layout: string &log &optional;
|
||||
## RDP client version used by the client machine.
|
||||
client_build: string &log &optional;
|
||||
## Name of the client machine.
|
||||
client_name: string &log &optional;
|
||||
## Product ID of the client machine.
|
||||
client_dig_product_id: string &log &optional;
|
||||
## Desktop width of the client machine.
|
||||
desktop_width: count &log &optional;
|
||||
## Desktop height of the client machine.
|
||||
desktop_height: count &log &optional;
|
||||
## The color depth requested by the client in
|
||||
## the high_color_depth field.
|
||||
requested_color_depth: string &log &optional;
|
||||
|
||||
## If the connection is being encrypted with native
|
||||
## RDP encryption, this is the type of cert
|
||||
## being used.
|
||||
cert_type: string &log &optional;
|
||||
## The number of certs seen. X.509 can transfer an
|
||||
## entire certificate chain.
|
||||
cert_count: count &log &default=0;
|
||||
## Indicates if the provided certificate or certificate
|
||||
## chain is permanent or temporary.
|
||||
cert_permanent: bool &log &optional;
|
||||
## Encryption level of the connection.
|
||||
encryption_level: string &log &optional;
|
||||
## Encryption method of the connection.
|
||||
encryption_method: string &log &optional;
|
||||
};
|
||||
|
||||
## If true, detach the RDP analyzer from the connection to prevent
|
||||
## continuing to process encrypted traffic.
|
||||
const disable_analyzer_after_detection = F &redef;
|
||||
|
||||
## The amount of time to monitor an RDP session from when it is first
|
||||
## identified. When this interval is reached, the session is logged.
|
||||
const rdp_check_interval = 10secs &redef;
|
||||
|
||||
## Event that can be handled to access the rdp record as it is sent on
|
||||
## to the logging framework.
|
||||
global log_rdp: event(rec: Info);
|
||||
}
|
||||
|
||||
# Internal fields that aren't useful externally
|
||||
redef record Info += {
|
||||
## The analyzer ID used for the analyzer instance attached
|
||||
## to each connection. It is not used for logging since it's a
|
||||
## meaningless arbitrary number.
|
||||
analyzer_id: count &optional;
|
||||
## Track status of logging RDP connections.
|
||||
done: bool &default=F;
|
||||
};
|
||||
|
||||
redef record connection += {
|
||||
rdp: Info &optional;
|
||||
};
|
||||
|
||||
const ports = { 3389/tcp };
|
||||
redef likely_server_ports += { ports };
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(RDP::LOG, [$columns=RDP::Info, $ev=log_rdp, $path="rdp"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_RDP, ports);
|
||||
}
|
||||
|
||||
function write_log(c: connection)
|
||||
{
|
||||
local info = c$rdp;
|
||||
|
||||
if ( info$done )
|
||||
return;
|
||||
|
||||
# Mark this record as fully logged and finished.
|
||||
info$done = T;
|
||||
|
||||
# Verify that the RDP session contains
|
||||
# RDP data before writing it to the log.
|
||||
if ( info?$cookie || info?$keyboard_layout || info?$result )
|
||||
Log::write(RDP::LOG, info);
|
||||
}
|
||||
|
||||
event check_record(c: connection)
|
||||
{
|
||||
# If the record was logged, then stop processing.
|
||||
if ( c$rdp$done )
|
||||
return;
|
||||
|
||||
# If the value rdp_check_interval has passed since the
|
||||
# RDP session was started, then log the record.
|
||||
local diff = network_time() - c$rdp$ts;
|
||||
if ( diff > rdp_check_interval )
|
||||
{
|
||||
write_log(c);
|
||||
|
||||
# Remove the analyzer if it is still attached.
|
||||
if ( disable_analyzer_after_detection &&
|
||||
connection_exists(c$id) &&
|
||||
c$rdp?$analyzer_id )
|
||||
{
|
||||
disable_analyzer(c$id, c$rdp$analyzer_id);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
# If the analyzer is attached and the duration
|
||||
# to monitor the RDP session was not met, then
|
||||
# reschedule the logging event.
|
||||
schedule rdp_check_interval { check_record(c) };
|
||||
}
|
||||
}
|
||||
|
||||
function set_session(c: connection)
|
||||
{
|
||||
if ( ! c?$rdp )
|
||||
{
|
||||
c$rdp = [$ts=network_time(),$id=c$id,$uid=c$uid];
|
||||
# The RDP session is scheduled to be logged from
|
||||
# the time it is first initiated.
|
||||
schedule rdp_check_interval { check_record(c) };
|
||||
}
|
||||
}
|
||||
|
||||
event rdp_connect_request(c: connection, cookie: string) &priority=5
|
||||
{
|
||||
set_session(c);
|
||||
|
||||
c$rdp$cookie = cookie;
|
||||
}
|
||||
|
||||
event rdp_negotiation_response(c: connection, security_protocol: count) &priority=5
|
||||
{
|
||||
set_session(c);
|
||||
|
||||
c$rdp$security_protocol = security_protocols[security_protocol];
|
||||
}
|
||||
|
||||
event rdp_negotiation_failure(c: connection, failure_code: count) &priority=5
|
||||
{
|
||||
set_session(c);
|
||||
|
||||
c$rdp$result = failure_codes[failure_code];
|
||||
}
|
||||
|
||||
event rdp_client_core_data(c: connection, data: RDP::ClientCoreData) &priority=5
|
||||
{
|
||||
set_session(c);
|
||||
|
||||
c$rdp$keyboard_layout = RDP::languages[data$keyboard_layout];
|
||||
c$rdp$client_build = RDP::builds[data$client_build];
|
||||
c$rdp$client_name = data$client_name;
|
||||
c$rdp$client_dig_product_id = data$dig_product_id;
|
||||
c$rdp$desktop_width = data$desktop_width;
|
||||
c$rdp$desktop_height = data$desktop_height;
|
||||
|
||||
if ( data?$ec_flags && data$ec_flags$want_32bpp_session )
|
||||
c$rdp$requested_color_depth = "32bit";
|
||||
else
|
||||
c$rdp$requested_color_depth = RDP::high_color_depths[data$high_color_depth];
|
||||
}
|
||||
|
||||
event rdp_gcc_server_create_response(c: connection, result: count) &priority=5
|
||||
{
|
||||
set_session(c);
|
||||
|
||||
c$rdp$result = RDP::results[result];
|
||||
}
|
||||
|
||||
event rdp_server_security(c: connection, encryption_method: count, encryption_level: count) &priority=5
|
||||
{
|
||||
set_session(c);
|
||||
|
||||
c$rdp$encryption_method = RDP::encryption_methods[encryption_method];
|
||||
c$rdp$encryption_level = RDP::encryption_levels[encryption_level];
|
||||
}
|
||||
|
||||
event rdp_server_certificate(c: connection, cert_type: count, permanently_issued: bool) &priority=5
|
||||
{
|
||||
set_session(c);
|
||||
|
||||
c$rdp$cert_type = RDP::cert_types[cert_type];
|
||||
|
||||
# There are no events for proprietary/RSA certs right
|
||||
# now so we manually count this one.
|
||||
if ( c$rdp$cert_type == "RSA" )
|
||||
++c$rdp$cert_count;
|
||||
|
||||
c$rdp$cert_permanent = permanently_issued;
|
||||
}
|
||||
|
||||
event rdp_begin_encryption(c: connection, security_protocol: count) &priority=5
|
||||
{
|
||||
set_session(c);
|
||||
|
||||
if ( ! c$rdp?$result )
|
||||
{
|
||||
c$rdp$result = "encrypted";
|
||||
}
|
||||
|
||||
c$rdp$security_protocol = security_protocols[security_protocol];
|
||||
}
|
||||
|
||||
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5
|
||||
{
|
||||
if ( c?$rdp && f$source == "RDP" )
|
||||
{
|
||||
# Count up X509 certs.
|
||||
++c$rdp$cert_count;
|
||||
|
||||
Files::add_analyzer(f, Files::ANALYZER_X509);
|
||||
Files::add_analyzer(f, Files::ANALYZER_MD5);
|
||||
Files::add_analyzer(f, Files::ANALYZER_SHA1);
|
||||
}
|
||||
}
|
||||
|
||||
event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &priority=5
|
||||
{
|
||||
if ( atype == Analyzer::ANALYZER_RDP )
|
||||
{
|
||||
set_session(c);
|
||||
c$rdp$analyzer_id = aid;
|
||||
}
|
||||
}
|
||||
|
||||
event protocol_violation(c: connection, atype: Analyzer::Tag, aid: count, reason: string) &priority=5
|
||||
{
|
||||
# If a protocol violation occurs, then log the record immediately.
|
||||
if ( c?$rdp )
|
||||
write_log(c);
|
||||
}
|
||||
|
||||
event connection_state_remove(c: connection) &priority=-5
|
||||
{
|
||||
# If the connection is removed, then log the record immediately.
|
||||
if ( c?$rdp )
|
||||
{
|
||||
write_log(c);
|
||||
}
|
||||
}
|
|
@ -92,13 +92,13 @@ redef likely_server_ports += { ports };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(SMTP::LOG, [$columns=SMTP::Info, $ev=log_smtp]);
|
||||
Log::create_stream(SMTP::LOG, [$columns=SMTP::Info, $ev=log_smtp, $path="smtp"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_SMTP, ports);
|
||||
}
|
||||
|
||||
function find_address_in_smtp_header(header: string): string
|
||||
{
|
||||
local ips = find_ip_addresses(header);
|
||||
local ips = extract_ip_addresses(header);
|
||||
# If there are more than one IP address found, return the second.
|
||||
if ( |ips| > 1 )
|
||||
return ips[1];
|
||||
|
@ -163,7 +163,7 @@ event smtp_request(c: connection, is_orig: bool, command: string, arg: string) &
|
|||
{
|
||||
if ( ! c$smtp?$rcptto )
|
||||
c$smtp$rcptto = set();
|
||||
add c$smtp$rcptto[split1(arg, /:[[:blank:]]*/)[2]];
|
||||
add c$smtp$rcptto[split_string1(arg, /:[[:blank:]]*/)[1]];
|
||||
c$smtp$has_client_activity = T;
|
||||
}
|
||||
|
||||
|
@ -172,8 +172,8 @@ event smtp_request(c: connection, is_orig: bool, command: string, arg: string) &
|
|||
# Flush last message in case we didn't see the server's acknowledgement.
|
||||
smtp_message(c);
|
||||
|
||||
local partially_done = split1(arg, /:[[:blank:]]*/)[2];
|
||||
c$smtp$mailfrom = split1(partially_done, /[[:blank:]]?/)[1];
|
||||
local partially_done = split_string1(arg, /:[[:blank:]]*/)[1];
|
||||
c$smtp$mailfrom = split_string1(partially_done, /[[:blank:]]?/)[0];
|
||||
c$smtp$has_client_activity = T;
|
||||
}
|
||||
}
|
||||
|
@ -234,14 +234,14 @@ event mime_one_header(c: connection, h: mime_header_rec) &priority=5
|
|||
if ( ! c$smtp?$to )
|
||||
c$smtp$to = set();
|
||||
|
||||
local to_parts = split(h$value, /[[:blank:]]*,[[:blank:]]*/);
|
||||
local to_parts = split_string(h$value, /[[:blank:]]*,[[:blank:]]*/);
|
||||
for ( i in to_parts )
|
||||
add c$smtp$to[to_parts[i]];
|
||||
}
|
||||
|
||||
else if ( h$name == "X-ORIGINATING-IP" )
|
||||
{
|
||||
local addresses = find_ip_addresses(h$value);
|
||||
local addresses = extract_ip_addresses(h$value);
|
||||
if ( 1 in addresses )
|
||||
c$smtp$x_originating_ip = to_addr(addresses[1]);
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ redef likely_server_ports += { ports };
|
|||
event bro_init() &priority=5
|
||||
{
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_SNMP, ports);
|
||||
Log::create_stream(SNMP::LOG, [$columns=SNMP::Info, $ev=log_snmp]);
|
||||
Log::create_stream(SNMP::LOG, [$columns=SNMP::Info, $ev=log_snmp, $path="snmp"]);
|
||||
}
|
||||
|
||||
function init_state(c: connection, h: SNMP::Header): Info
|
||||
|
|
|
@ -16,8 +16,10 @@ export {
|
|||
id: conn_id &log;
|
||||
## Protocol version of SOCKS.
|
||||
version: count &log;
|
||||
## Username for the proxy if extracted from the network.
|
||||
## Username used to request a login to the proxy.
|
||||
user: string &log &optional;
|
||||
## Password used to request a login to the proxy.
|
||||
password: string &log &optional;
|
||||
## Server status for the attempt at using the proxy.
|
||||
status: string &log &optional;
|
||||
## Client requested SOCKS address. Could be an address, a name
|
||||
|
@ -41,7 +43,7 @@ redef likely_server_ports += { ports };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(SOCKS::LOG, [$columns=Info, $ev=log_socks]);
|
||||
Log::create_stream(SOCKS::LOG, [$columns=Info, $ev=log_socks, $path="socks"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_SOCKS, ports);
|
||||
}
|
||||
|
||||
|
@ -91,3 +93,21 @@ event socks_reply(c: connection, version: count, reply: count, sa: SOCKS::Addres
|
|||
if ( "SOCKS" in c$service )
|
||||
Log::write(SOCKS::LOG, c$socks);
|
||||
}
|
||||
|
||||
event socks_login_userpass_request(c: connection, user: string, password: string) &priority=5
|
||||
{
|
||||
# Authentication only possible with the version 5.
|
||||
set_session(c, 5);
|
||||
|
||||
c$socks$user = user;
|
||||
c$socks$password = password;
|
||||
}
|
||||
|
||||
event socks_login_userpass_reply(c: connection, code: count) &priority=5
|
||||
{
|
||||
# Authentication only possible with the version 5.
|
||||
set_session(c, 5);
|
||||
|
||||
c$socks$status = v5_status[code];
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Support for Secure Shell (SSH) protocol analysis.
|
|
@ -1,3 +1,2 @@
|
|||
@load ./main
|
||||
|
||||
@load-sigs ./dpd.sig
|
||||
@load-sigs ./dpd.sig
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
signature dpd_ssh_client {
|
||||
ip-proto == tcp
|
||||
payload /^[sS][sS][hH]-/
|
||||
payload /^[sS][sS][hH]-[12]\./
|
||||
requires-reverse-signature dpd_ssh_server
|
||||
enable "ssh"
|
||||
tcp-state originator
|
||||
|
@ -8,6 +8,6 @@ signature dpd_ssh_client {
|
|||
|
||||
signature dpd_ssh_server {
|
||||
ip-proto == tcp
|
||||
payload /^[sS][sS][hH]-/
|
||||
payload /^[sS][sS][hH]-[12]\./
|
||||
tcp-state responder
|
||||
}
|
||||
}
|
|
@ -1,15 +1,5 @@
|
|||
##! Base SSH analysis script. The heuristic to blindly determine success or
|
||||
##! failure for SSH connections is implemented here. At this time, it only
|
||||
##! uses the size of the data being returned from the server to make the
|
||||
##! heuristic determination about success of the connection.
|
||||
##! Requires that :bro:id:`use_conn_size_analyzer` is set to T! The heuristic
|
||||
##! is not attempted if the connection size analyzer isn't enabled.
|
||||
##! Implements base functionality for SSH analysis. Generates the ssh.log file.
|
||||
|
||||
@load base/protocols/conn
|
||||
@load base/frameworks/notice
|
||||
@load base/utils/site
|
||||
@load base/utils/thresholds
|
||||
@load base/utils/conn-ids
|
||||
@load base/utils/directions-and-hosts
|
||||
|
||||
module SSH;
|
||||
|
@ -25,45 +15,63 @@ export {
|
|||
uid: string &log;
|
||||
## The connection's 4-tuple of endpoint addresses/ports.
|
||||
id: conn_id &log;
|
||||
## Indicates if the login was heuristically guessed to be
|
||||
## "success", "failure", or "undetermined".
|
||||
status: string &log &default="undetermined";
|
||||
## Direction of the connection. If the client was a local host
|
||||
## SSH major version (1 or 2)
|
||||
version: count &log;
|
||||
## Authentication result (T=success, F=failure, unset=unknown)
|
||||
auth_success: bool &log &optional;
|
||||
## Direction of the connection. If the client was a local host
|
||||
## logging into an external host, this would be OUTBOUND. INBOUND
|
||||
## would be set for the opposite situation.
|
||||
# TODO: handle local-local and remote-remote better.
|
||||
# TODO - handle local-local and remote-remote better.
|
||||
direction: Direction &log &optional;
|
||||
## Software string from the client.
|
||||
## The client's version string
|
||||
client: string &log &optional;
|
||||
## Software string from the server.
|
||||
## The server's version string
|
||||
server: string &log &optional;
|
||||
## Indicate if the SSH session is done being watched.
|
||||
done: bool &default=F;
|
||||
## The encryption algorithm in use
|
||||
cipher_alg: string &log &optional;
|
||||
## The signing (MAC) algorithm in use
|
||||
mac_alg: string &log &optional;
|
||||
## The compression algorithm in use
|
||||
compression_alg: string &log &optional;
|
||||
## The key exchange algorithm in use
|
||||
kex_alg: string &log &optional;
|
||||
## The server host key's algorithm
|
||||
host_key_alg: string &log &optional;
|
||||
## The server's key fingerprint
|
||||
host_key: string &log &optional;
|
||||
};
|
||||
|
||||
## The size in bytes of data sent by the server at which the SSH
|
||||
## connection is presumed to be successful.
|
||||
const authentication_data_size = 4000 &redef;
|
||||
## The set of compression algorithms. We can't accurately determine
|
||||
## authentication success or failure when compression is enabled.
|
||||
const compression_algorithms = set("zlib", "zlib@openssh.com") &redef;
|
||||
|
||||
## If true, we tell the event engine to not look at further data
|
||||
## packets after the initial SSH handshake. Helps with performance
|
||||
## (especially with large file transfers) but precludes some
|
||||
## kinds of analyses.
|
||||
const skip_processing_after_detection = F &redef;
|
||||
## kinds of analyses. Defaults to T.
|
||||
const skip_processing_after_detection = T &redef;
|
||||
|
||||
## Event that is generated when the heuristic thinks that a login
|
||||
## was successful.
|
||||
global heuristic_successful_login: event(c: connection);
|
||||
|
||||
## Event that is generated when the heuristic thinks that a login
|
||||
## failed.
|
||||
global heuristic_failed_login: event(c: connection);
|
||||
|
||||
## Event that can be handled to access the :bro:type:`SSH::Info`
|
||||
## record as it is sent on to the logging framework.
|
||||
## Event that can be handled to access the SSH record as it is sent on
|
||||
## to the logging framework.
|
||||
global log_ssh: event(rec: Info);
|
||||
|
||||
## Event that can be handled when the analyzer sees an SSH server host
|
||||
## key. This abstracts :bro:id:`ssh1_server_host_key` and
|
||||
## :bro:id:`ssh2_server_host_key`.
|
||||
global ssh_server_host_key: event(c: connection, hash: string);
|
||||
}
|
||||
|
||||
redef record Info += {
|
||||
# This connection has been logged (internal use)
|
||||
logged: bool &default=F;
|
||||
# Number of failures seen (internal use)
|
||||
num_failures: count &default=0;
|
||||
# Store capabilities from the first host for
|
||||
# comparison with the second (internal use)
|
||||
capabilities: Capabilities &optional;
|
||||
};
|
||||
|
||||
redef record connection += {
|
||||
ssh: Info &optional;
|
||||
};
|
||||
|
@ -72,133 +80,152 @@ const ports = { 22/tcp };
|
|||
redef likely_server_ports += { ports };
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(SSH::LOG, [$columns=Info, $ev=log_ssh]);
|
||||
{
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_SSH, ports);
|
||||
}
|
||||
Log::create_stream(SSH::LOG, [$columns=Info, $ev=log_ssh, $path="ssh"]);
|
||||
}
|
||||
|
||||
function set_session(c: connection)
|
||||
{
|
||||
if ( ! c?$ssh )
|
||||
{
|
||||
local info: Info;
|
||||
info$ts=network_time();
|
||||
info$uid=c$uid;
|
||||
info$id=c$id;
|
||||
local info: SSH::Info;
|
||||
info$ts = network_time();
|
||||
info$uid = c$uid;
|
||||
info$id = c$id;
|
||||
c$ssh = info;
|
||||
}
|
||||
}
|
||||
|
||||
function check_ssh_connection(c: connection, done: bool)
|
||||
{
|
||||
# If already done watching this connection, just return.
|
||||
if ( c$ssh$done )
|
||||
return;
|
||||
|
||||
if ( done )
|
||||
{
|
||||
# If this connection is done, then we can look to see if
|
||||
# this matches the conditions for a failed login. Failed
|
||||
# logins are only detected at connection state removal.
|
||||
|
||||
if ( # Require originators and responders to have sent at least 50 bytes.
|
||||
c$orig$size > 50 && c$resp$size > 50 &&
|
||||
# Responders must be below 4000 bytes.
|
||||
c$resp$size < authentication_data_size &&
|
||||
# Responder must have sent fewer than 40 packets.
|
||||
c$resp$num_pkts < 40 &&
|
||||
# If there was a content gap we can't reliably do this heuristic.
|
||||
c?$conn && c$conn$missed_bytes == 0 )# &&
|
||||
# Only "normal" connections can count.
|
||||
#c$conn?$conn_state && c$conn$conn_state in valid_states )
|
||||
{
|
||||
c$ssh$status = "failure";
|
||||
event SSH::heuristic_failed_login(c);
|
||||
}
|
||||
|
||||
if ( c$resp$size >= authentication_data_size )
|
||||
{
|
||||
c$ssh$status = "success";
|
||||
event SSH::heuristic_successful_login(c);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
# If this connection is still being tracked, then it's possible
|
||||
# to watch for it to be a successful connection.
|
||||
if ( c$resp$size >= authentication_data_size )
|
||||
{
|
||||
c$ssh$status = "success";
|
||||
event SSH::heuristic_successful_login(c);
|
||||
}
|
||||
else
|
||||
# This connection must be tracked longer. Let the scheduled
|
||||
# check happen again.
|
||||
return;
|
||||
}
|
||||
|
||||
# Set the direction for the log.
|
||||
c$ssh$direction = Site::is_local_addr(c$id$orig_h) ? OUTBOUND : INBOUND;
|
||||
|
||||
# Set the "done" flag to prevent the watching event from rescheduling
|
||||
# after detection is done.
|
||||
c$ssh$done=T;
|
||||
|
||||
if ( skip_processing_after_detection )
|
||||
{
|
||||
# Stop watching this connection, we don't care about it anymore.
|
||||
skip_further_processing(c$id);
|
||||
set_record_packets(c$id, F);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
event heuristic_successful_login(c: connection) &priority=-5
|
||||
{
|
||||
Log::write(SSH::LOG, c$ssh);
|
||||
}
|
||||
|
||||
event heuristic_failed_login(c: connection) &priority=-5
|
||||
{
|
||||
Log::write(SSH::LOG, c$ssh);
|
||||
}
|
||||
|
||||
event connection_state_remove(c: connection) &priority=-5
|
||||
{
|
||||
if ( c?$ssh )
|
||||
{
|
||||
check_ssh_connection(c, T);
|
||||
if ( c$ssh$status == "undetermined" )
|
||||
Log::write(SSH::LOG, c$ssh);
|
||||
}
|
||||
}
|
||||
|
||||
event ssh_watcher(c: connection)
|
||||
{
|
||||
local id = c$id;
|
||||
# don't go any further if this connection is gone already!
|
||||
if ( ! connection_exists(id) )
|
||||
return;
|
||||
|
||||
lookup_connection(c$id);
|
||||
check_ssh_connection(c, F);
|
||||
if ( ! c$ssh$done )
|
||||
schedule +15secs { ssh_watcher(c) };
|
||||
}
|
||||
|
||||
event ssh_server_version(c: connection, version: string) &priority=5
|
||||
event ssh_server_version(c: connection, version: string)
|
||||
{
|
||||
set_session(c);
|
||||
c$ssh$server = version;
|
||||
}
|
||||
|
||||
event ssh_client_version(c: connection, version: string) &priority=5
|
||||
event ssh_client_version(c: connection, version: string)
|
||||
{
|
||||
set_session(c);
|
||||
c$ssh$client = version;
|
||||
|
||||
# The heuristic detection for SSH relies on the ConnSize analyzer.
|
||||
# Don't do the heuristics if it's disabled.
|
||||
if ( use_conn_size_analyzer )
|
||||
schedule +15secs { ssh_watcher(c) };
|
||||
if ( ( |version| > 3 ) && ( version[4] == "1" ) )
|
||||
c$ssh$version = 1;
|
||||
if ( ( |version| > 3 ) && ( version[4] == "2" ) )
|
||||
c$ssh$version = 2;
|
||||
}
|
||||
|
||||
event ssh_auth_successful(c: connection, auth_method_none: bool)
|
||||
{
|
||||
# TODO - what to do here?
|
||||
if ( !c?$ssh || ( c$ssh?$auth_success && c$ssh$auth_success ) )
|
||||
return;
|
||||
|
||||
# We can't accurately tell for compressed streams
|
||||
if ( c$ssh?$compression_alg && ( c$ssh$compression_alg in compression_algorithms ) )
|
||||
return;
|
||||
|
||||
c$ssh$auth_success = T;
|
||||
|
||||
if ( skip_processing_after_detection)
|
||||
{
|
||||
skip_further_processing(c$id);
|
||||
set_record_packets(c$id, F);
|
||||
}
|
||||
}
|
||||
|
||||
event ssh_auth_successful(c: connection, auth_method_none: bool) &priority=-5
|
||||
{
|
||||
if ( c?$ssh && !c$ssh$logged )
|
||||
{
|
||||
c$ssh$logged = T;
|
||||
Log::write(SSH::LOG, c$ssh);
|
||||
}
|
||||
}
|
||||
|
||||
event ssh_auth_failed(c: connection)
|
||||
{
|
||||
if ( !c?$ssh || ( c$ssh?$auth_success && !c$ssh$auth_success ) )
|
||||
return;
|
||||
|
||||
# We can't accurately tell for compressed streams
|
||||
if ( c$ssh?$compression_alg && ( c$ssh$compression_alg in compression_algorithms ) )
|
||||
return;
|
||||
|
||||
c$ssh$auth_success = F;
|
||||
c$ssh$num_failures += 1;
|
||||
}
|
||||
|
||||
# Determine the negotiated algorithm
|
||||
function find_alg(client_algorithms: vector of string, server_algorithms: vector of string): string
|
||||
{
|
||||
for ( i in client_algorithms )
|
||||
for ( j in server_algorithms )
|
||||
if ( client_algorithms[i] == server_algorithms[j] )
|
||||
return client_algorithms[i];
|
||||
return "Algorithm negotiation failed";
|
||||
}
|
||||
|
||||
# This is a simple wrapper around find_alg for cases where client to server and server to client
|
||||
# negotiate different algorithms. This is rare, but provided for completeness.
|
||||
function find_bidirectional_alg(client_prefs: Algorithm_Prefs, server_prefs: Algorithm_Prefs): string
|
||||
{
|
||||
local c_to_s = find_alg(client_prefs$client_to_server, server_prefs$client_to_server);
|
||||
local s_to_c = find_alg(client_prefs$server_to_client, server_prefs$server_to_client);
|
||||
|
||||
# Usually these are the same, but if they're not, return the details
|
||||
return c_to_s == s_to_c ? c_to_s : fmt("To server: %s, to client: %s", c_to_s, s_to_c);
|
||||
}
|
||||
|
||||
event ssh_capabilities(c: connection, cookie: string, capabilities: Capabilities)
|
||||
{
|
||||
if ( !c?$ssh || ( c$ssh?$capabilities && c$ssh$capabilities$is_server == capabilities$is_server ) )
|
||||
return;
|
||||
|
||||
if ( !c$ssh?$capabilities )
|
||||
{
|
||||
c$ssh$capabilities = capabilities;
|
||||
return;
|
||||
}
|
||||
|
||||
local client_caps = capabilities$is_server ? c$ssh$capabilities : capabilities;
|
||||
local server_caps = capabilities$is_server ? capabilities : c$ssh$capabilities;
|
||||
|
||||
c$ssh$cipher_alg = find_bidirectional_alg(client_caps$encryption_algorithms,
|
||||
server_caps$encryption_algorithms);
|
||||
c$ssh$mac_alg = find_bidirectional_alg(client_caps$mac_algorithms,
|
||||
server_caps$mac_algorithms);
|
||||
c$ssh$compression_alg = find_bidirectional_alg(client_caps$compression_algorithms,
|
||||
server_caps$compression_algorithms);
|
||||
c$ssh$kex_alg = find_alg(client_caps$kex_algorithms, server_caps$kex_algorithms);
|
||||
c$ssh$host_key_alg = find_alg(client_caps$server_host_key_algorithms,
|
||||
server_caps$server_host_key_algorithms);
|
||||
}
|
||||
|
||||
event connection_state_remove(c: connection) &priority=-5
|
||||
{
|
||||
if ( c?$ssh && !c$ssh$logged && c$ssh?$client && c$ssh?$server )
|
||||
{
|
||||
c$ssh$logged = T;
|
||||
Log::write(SSH::LOG, c$ssh);
|
||||
}
|
||||
}
|
||||
|
||||
function generate_fingerprint(c: connection, key: string)
|
||||
{
|
||||
if ( !c?$ssh )
|
||||
return;
|
||||
|
||||
local lx = str_split(md5_hash(key), vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30));
|
||||
lx[0] = "";
|
||||
c$ssh$host_key = sub(join_string_vec(lx, ":"), /:/, "");
|
||||
}
|
||||
|
||||
event ssh1_server_host_key(c: connection, p: string, e: string) &priority=5
|
||||
{
|
||||
generate_fingerprint(c, e + p);
|
||||
}
|
||||
|
||||
event ssh2_server_host_key(c: connection, key: string) &priority=5
|
||||
{
|
||||
generate_fingerprint(c, key);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,11 @@ export {
|
|||
const TLSv10 = 0x0301;
|
||||
const TLSv11 = 0x0302;
|
||||
const TLSv12 = 0x0303;
|
||||
|
||||
const DTLSv10 = 0xFEFF;
|
||||
# DTLSv11 does not exist
|
||||
const DTLSv12 = 0xFEFD;
|
||||
|
||||
## Mapping between the constants and string values for SSL/TLS versions.
|
||||
const version_strings: table[count] of string = {
|
||||
[SSLv2] = "SSLv2",
|
||||
|
@ -13,6 +18,8 @@ export {
|
|||
[TLSv10] = "TLSv10",
|
||||
[TLSv11] = "TLSv11",
|
||||
[TLSv12] = "TLSv12",
|
||||
[DTLSv10] = "DTLSv10",
|
||||
[DTLSv12] = "DTLSv12"
|
||||
} &default=function(i: count):string { return fmt("unknown-%d", i); };
|
||||
|
||||
## TLS content types:
|
||||
|
|
|
@ -13,3 +13,10 @@ signature dpd_ssl_client {
|
|||
payload /^(\x16\x03[\x00\x01\x02\x03]..\x01...\x03[\x00\x01\x02\x03]|...?\x01[\x00\x03][\x00\x01\x02\x03]).*/
|
||||
tcp-state originator
|
||||
}
|
||||
|
||||
signature dpd_dtls_client {
|
||||
ip-proto == udp
|
||||
# Client hello.
|
||||
payload /^\x16\xfe[\xff\xfd]\x00\x00\x00\x00\x00\x00\x00...\x01...........\xfe[\xff\xfd].*/
|
||||
enable "dtls"
|
||||
}
|
||||
|
|
|
@ -85,6 +85,10 @@ event bro_init() &priority=5
|
|||
Files::register_protocol(Analyzer::ANALYZER_SSL,
|
||||
[$get_file_handle = SSL::get_file_handle,
|
||||
$describe = SSL::describe_file]);
|
||||
|
||||
Files::register_protocol(Analyzer::ANALYZER_DTLS,
|
||||
[$get_file_handle = SSL::get_file_handle,
|
||||
$describe = SSL::describe_file]);
|
||||
}
|
||||
|
||||
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5
|
||||
|
|
|
@ -92,16 +92,22 @@ redef record Info += {
|
|||
delay_tokens: set[string] &optional;
|
||||
};
|
||||
|
||||
const ports = {
|
||||
const ssl_ports = {
|
||||
443/tcp, 563/tcp, 585/tcp, 614/tcp, 636/tcp,
|
||||
989/tcp, 990/tcp, 992/tcp, 993/tcp, 995/tcp, 5223/tcp
|
||||
};
|
||||
redef likely_server_ports += { ports };
|
||||
|
||||
# There are no well known DTLS ports at the moment. Let's
|
||||
# just add 443 for now for good measure - who knows :)
|
||||
const dtls_ports = { 443/udp };
|
||||
|
||||
redef likely_server_ports += { ssl_ports, dtls_ports };
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(SSL::LOG, [$columns=Info, $ev=log_ssl]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_SSL, ports);
|
||||
Log::create_stream(SSL::LOG, [$columns=Info, $ev=log_ssl, $path="ssl"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_SSL, ssl_ports);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_DTLS, dtls_ports);
|
||||
}
|
||||
|
||||
function set_session(c: connection)
|
||||
|
@ -268,7 +274,7 @@ event connection_state_remove(c: connection) &priority=-5
|
|||
|
||||
event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &priority=5
|
||||
{
|
||||
if ( atype == Analyzer::ANALYZER_SSL )
|
||||
if ( atype == Analyzer::ANALYZER_SSL || atype == Analyzer::ANALYZER_DTLS )
|
||||
{
|
||||
set_session(c);
|
||||
c$ssl$analyzer_id = aid;
|
||||
|
@ -278,6 +284,6 @@ event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &pr
|
|||
event protocol_violation(c: connection, atype: Analyzer::Tag, aid: count,
|
||||
reason: string) &priority=5
|
||||
{
|
||||
if ( c?$ssl )
|
||||
if ( c?$ssl && ( atype == Analyzer::ANALYZER_SSL || atype == Analyzer::ANALYZER_DTLS ) )
|
||||
finish(c, T);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ redef likely_server_ports += { ports };
|
|||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Syslog::LOG, [$columns=Info]);
|
||||
Log::create_stream(Syslog::LOG, [$columns=Info, $path="syslog"]);
|
||||
Analyzer::register_for_ports(Analyzer::ANALYZER_SYSLOG, ports);
|
||||
}
|
||||
|
||||
|
|
|
@ -105,21 +105,21 @@ function request(req: Request): ActiveHTTP::Response
|
|||
# The reply is the first line.
|
||||
if ( i == 0 )
|
||||
{
|
||||
local response_line = split_n(headers[0], /[[:blank:]]+/, F, 2);
|
||||
local response_line = split_string_n(headers[0], /[[:blank:]]+/, F, 2);
|
||||
if ( |response_line| != 3 )
|
||||
return resp;
|
||||
|
||||
resp$code = to_count(response_line[2]);
|
||||
resp$msg = response_line[3];
|
||||
resp$code = to_count(response_line[1]);
|
||||
resp$msg = response_line[2];
|
||||
resp$body = join_string_vec(result$files[bodyfile], "");
|
||||
}
|
||||
else
|
||||
{
|
||||
local line = headers[i];
|
||||
local h = split1(line, /:/);
|
||||
local h = split_string1(line, /:/);
|
||||
if ( |h| != 2 )
|
||||
next;
|
||||
resp$headers[h[1]] = sub_bytes(h[2], 0, |h[2]|-1);
|
||||
resp$headers[h[0]] = sub_bytes(h[1], 0, |h[1]|-1);
|
||||
}
|
||||
}
|
||||
return resp;
|
||||
|
|
|
@ -32,7 +32,7 @@ const ip_addr_regex =
|
|||
## octets: an array of strings to check for valid octet values.
|
||||
##
|
||||
## Returns: T if every element is between 0 and 255, inclusive, else F.
|
||||
function has_valid_octets(octets: string_array): bool
|
||||
function has_valid_octets(octets: string_vec): bool
|
||||
{
|
||||
local num = 0;
|
||||
for ( i in octets )
|
||||
|
@ -51,10 +51,10 @@ function has_valid_octets(octets: string_array): bool
|
|||
## Returns: T if the string is a valid IPv4 or IPv6 address format.
|
||||
function is_valid_ip(ip_str: string): bool
|
||||
{
|
||||
local octets: string_array;
|
||||
local octets: string_vec;
|
||||
if ( ip_str == ipv4_addr_regex )
|
||||
{
|
||||
octets = split(ip_str, /\./);
|
||||
octets = split_string(ip_str, /\./);
|
||||
if ( |octets| != 4 )
|
||||
return F;
|
||||
|
||||
|
@ -67,13 +67,13 @@ function is_valid_ip(ip_str: string): bool
|
|||
{
|
||||
# the regexes for hybrid IPv6-IPv4 address formats don't for valid
|
||||
# octets within the IPv4 part, so do that now
|
||||
octets = split(ip_str, /\./);
|
||||
octets = split_string(ip_str, /\./);
|
||||
if ( |octets| != 4 )
|
||||
return F;
|
||||
|
||||
# get rid of remaining IPv6 stuff in first octet
|
||||
local tmp = split(octets[1], /:/);
|
||||
octets[1] = tmp[|tmp|];
|
||||
local tmp = split_string(octets[0], /:/);
|
||||
octets[0] = tmp[|tmp| - 1];
|
||||
|
||||
return has_valid_octets(octets);
|
||||
}
|
||||
|
@ -92,14 +92,32 @@ function is_valid_ip(ip_str: string): bool
|
|||
## input: a string that may contain an IP address anywhere within it.
|
||||
##
|
||||
## Returns: an array containing all valid IP address strings found in *input*.
|
||||
function find_ip_addresses(input: string): string_array
|
||||
function find_ip_addresses(input: string): string_array &deprecated
|
||||
{
|
||||
local parts = split_all(input, ip_addr_regex);
|
||||
local parts = split_string_all(input, ip_addr_regex);
|
||||
local output: string_array;
|
||||
|
||||
for ( i in parts )
|
||||
{
|
||||
if ( i % 2 == 0 && is_valid_ip(parts[i]) )
|
||||
if ( i % 2 == 1 && is_valid_ip(parts[i]) )
|
||||
output[|output|] = parts[i];
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
## Extracts all IP (v4 or v6) address strings from a given string.
|
||||
##
|
||||
## input: a string that may contain an IP address anywhere within it.
|
||||
##
|
||||
## Returns: an array containing all valid IP address strings found in *input*.
|
||||
function extract_ip_addresses(input: string): string_vec
|
||||
{
|
||||
local parts = split_string_all(input, ip_addr_regex);
|
||||
local output: string_vec;
|
||||
|
||||
for ( i in parts )
|
||||
{
|
||||
if ( i % 2 == 1 && is_valid_ip(parts[i]) )
|
||||
output[|output|] = parts[i];
|
||||
}
|
||||
return output;
|
||||
|
|
|
@ -82,9 +82,9 @@ event Exec::line(description: Input::EventDescription, tpe: Input::Event, s: str
|
|||
|
||||
event Exec::file_line(description: Input::EventDescription, tpe: Input::Event, s: string)
|
||||
{
|
||||
local parts = split1(description$name, /_/);
|
||||
local name = parts[1];
|
||||
local track_file = parts[2];
|
||||
local parts = split_string1(description$name, /_/);
|
||||
local name = parts[0];
|
||||
local track_file = parts[1];
|
||||
|
||||
local result = results[name];
|
||||
if ( ! result?$files )
|
||||
|
@ -99,13 +99,13 @@ event Exec::file_line(description: Input::EventDescription, tpe: Input::Event, s
|
|||
event Input::end_of_data(orig_name: string, source:string)
|
||||
{
|
||||
local name = orig_name;
|
||||
local parts = split1(name, /_/);
|
||||
name = parts[1];
|
||||
local parts = split_string1(name, /_/);
|
||||
name = parts[0];
|
||||
|
||||
if ( name !in pending_commands || |parts| < 2 )
|
||||
return;
|
||||
|
||||
local track_file = parts[2];
|
||||
local track_file = parts[1];
|
||||
|
||||
# If the file is empty, still add it to the result$files table. This is needed
|
||||
# because it is expected that the file was read even if it was empty.
|
||||
|
|
|
@ -23,7 +23,7 @@ function extract_filename_from_content_disposition(data: string): string
|
|||
|
||||
# Remove quotes around the filename if they are there.
|
||||
if ( /^\"/ in filename )
|
||||
filename = split_n(filename, /\"/, F, 2)[2];
|
||||
filename = split_string_n(filename, /\"/, F, 2)[1];
|
||||
|
||||
# Remove the language and encoding if it's there.
|
||||
if ( /^[a-zA-Z0-9\!#$%&+-^_`{}~]+'[a-zA-Z0-9\!#$%&+-^_`{}~]*'/ in filename )
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
## If no integer can be found, 0 is returned.
|
||||
function extract_count(s: string): count
|
||||
{
|
||||
local parts = split_n(s, /[0-9]+/, T, 1);
|
||||
if ( 2 in parts )
|
||||
return to_count(parts[2]);
|
||||
local parts = split_string_n(s, /[0-9]+/, T, 1);
|
||||
if ( 1 in parts )
|
||||
return to_count(parts[1]);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@ const absolute_path_pat = /(\/|[A-Za-z]:[\\\/]).*/;
|
|||
function extract_path(input: string): string
|
||||
{
|
||||
const dir_pattern = /(\/|[A-Za-z]:[\\\/])([^\"\ ]|(\\\ ))*/;
|
||||
local parts = split_all(input, dir_pattern);
|
||||
local parts = split_string_all(input, dir_pattern);
|
||||
|
||||
if ( |parts| < 3 )
|
||||
return "";
|
||||
|
||||
return parts[2];
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
## Compresses a given path by removing '..'s and the parent directory it
|
||||
|
@ -31,27 +31,27 @@ function compress_path(dir: string): string
|
|||
{
|
||||
const cdup_sep = /((\/)*([^\/]|\\\/)+)?((\/)+\.\.(\/)*)/;
|
||||
|
||||
local parts = split_n(dir, cdup_sep, T, 1);
|
||||
local parts = split_string_n(dir, cdup_sep, T, 1);
|
||||
if ( |parts| > 1 )
|
||||
{
|
||||
# reaching a point with two parent dir references back-to-back means
|
||||
# we don't know about anything higher in the tree to pop off
|
||||
if ( parts[2] == "../.." )
|
||||
return cat_string_array(parts);
|
||||
if ( sub_bytes(parts[2], 0, 1) == "/" )
|
||||
parts[2] = "/";
|
||||
if ( parts[1] == "../.." )
|
||||
return join_string_vec(parts, "");
|
||||
if ( sub_bytes(parts[1], 0, 1) == "/" )
|
||||
parts[1] = "/";
|
||||
else
|
||||
parts[2] = "";
|
||||
dir = cat_string_array(parts);
|
||||
parts[1] = "";
|
||||
dir = join_string_vec(parts, "");
|
||||
return compress_path(dir);
|
||||
}
|
||||
|
||||
const multislash_sep = /(\/\.?){2,}/;
|
||||
parts = split_all(dir, multislash_sep);
|
||||
parts = split_string_all(dir, multislash_sep);
|
||||
for ( i in parts )
|
||||
if ( i % 2 == 0 )
|
||||
if ( i % 2 == 1 )
|
||||
parts[i] = "/";
|
||||
dir = cat_string_array(parts);
|
||||
dir = join_string_vec(parts, "");
|
||||
|
||||
# remove trailing slashes from path
|
||||
if ( |dir| > 1 && sub_bytes(dir, |dir|, 1) == "/" )
|
||||
|
|
|
@ -50,11 +50,11 @@ type PatternMatchResult: record {
|
|||
## Returns: a record indicating the match status.
|
||||
function match_pattern(s: string, p: pattern): PatternMatchResult
|
||||
{
|
||||
local a = split_n(s, p, T, 1);
|
||||
local a = split_string_n(s, p, T, 1);
|
||||
|
||||
if ( |a| == 1 )
|
||||
# no match
|
||||
return [$matched = F, $str = "", $off = 0];
|
||||
else
|
||||
return [$matched = T, $str = a[2], $off = |a[1]| + 1];
|
||||
return [$matched = T, $str = a[1], $off = |a[0]| + 1];
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ function find_all_urls_without_scheme(s: string): string_set
|
|||
|
||||
function decompose_uri(s: string): URI
|
||||
{
|
||||
local parts: string_array;
|
||||
local parts: string_vec;
|
||||
local u: URI = [$netlocation="", $path="/"];
|
||||
|
||||
if ( /\?/ in s)
|
||||
|
@ -56,55 +56,55 @@ function decompose_uri(s: string): URI
|
|||
# Parse query.
|
||||
u$params = table();
|
||||
|
||||
parts = split1(s, /\?/);
|
||||
s = parts[1];
|
||||
local query: string = parts[2];
|
||||
parts = split_string1(s, /\?/);
|
||||
s = parts[0];
|
||||
local query: string = parts[1];
|
||||
|
||||
if ( /&/ in query )
|
||||
{
|
||||
local opv: table[count] of string = split(query, /&/);
|
||||
local opv = split_string(query, /&/);
|
||||
|
||||
for ( each in opv )
|
||||
{
|
||||
if ( /=/ in opv[each] )
|
||||
{
|
||||
parts = split1(opv[each], /=/);
|
||||
u$params[parts[1]] = parts[2];
|
||||
parts = split_string1(opv[each], /=/);
|
||||
u$params[parts[0]] = parts[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parts = split1(query, /=/);
|
||||
u$params[parts[1]] = parts[2];
|
||||
parts = split_string1(query, /=/);
|
||||
u$params[parts[0]] = parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
if ( /:\/\// in s )
|
||||
{
|
||||
# Parse scheme and remove from s.
|
||||
parts = split1(s, /:\/\//);
|
||||
u$scheme = parts[1];
|
||||
s = parts[2];
|
||||
parts = split_string1(s, /:\/\//);
|
||||
u$scheme = parts[0];
|
||||
s = parts[1];
|
||||
}
|
||||
|
||||
if ( /\// in s )
|
||||
{
|
||||
# Parse path and remove from s.
|
||||
parts = split1(s, /\//);
|
||||
s = parts[1];
|
||||
u$path = fmt("/%s", parts[2]);
|
||||
parts = split_string1(s, /\//);
|
||||
s = parts[0];
|
||||
u$path = fmt("/%s", parts[1]);
|
||||
|
||||
if ( |u$path| > 1 && u$path[|u$path| - 1] != "/" )
|
||||
{
|
||||
local last_token: string = find_last(u$path, /\/.+/);
|
||||
local full_filename = split1(last_token, /\//)[2];
|
||||
local full_filename = split_string1(last_token, /\//)[1];
|
||||
|
||||
if ( /\./ in full_filename )
|
||||
{
|
||||
u$file_name = full_filename;
|
||||
u$file_base = split1(full_filename, /\./)[1];
|
||||
u$file_ext = split1(full_filename, /\./)[2];
|
||||
u$file_base = split_string1(full_filename, /\./)[0];
|
||||
u$file_ext = split_string1(full_filename, /\./)[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -117,9 +117,9 @@ function decompose_uri(s: string): URI
|
|||
if ( /:/ in s )
|
||||
{
|
||||
# Parse location and port.
|
||||
parts = split1(s, /:/);
|
||||
u$netlocation = parts[1];
|
||||
u$portnum = to_count(parts[2]);
|
||||
parts = split_string1(s, /:/);
|
||||
u$netlocation = parts[0];
|
||||
u$portnum = to_count(parts[1]);
|
||||
}
|
||||
else
|
||||
u$netlocation = s;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue