diff --git a/CHANGES b/CHANGES index cdd8457627..49b942fc85 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,8 @@ +2.3-631 | 2015-03-25 11:03:12 -0700 + + * New SSH analyzer. (Vlad Grigorescu) + 2.3-600 | 2015-03-25 10:23:46 -0700 * Add defensive checks in code to calculate log rotation intervals. diff --git a/NEWS b/NEWS index 6cfeb4f6ec..6767537170 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,10 @@ New Functionality - Bro now has supoprt for the MySQL wire protocol. Activity gets logged into mysql.log. +- Bro now features a completely rewritten, enhanced SSH analyzer. A lot + more information about SSH sessions is logged. The analyzer is able to + determine if logins failed or succeeded in most circumstances. + - Bro's file analysis now supports reassembly of files that are not transferred/seen sequentially. diff --git a/VERSION b/VERSION index 82128c2468..029742c5c6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.3-600 +2.3-631 diff --git a/scripts/base/frameworks/intel/main.bro b/scripts/base/frameworks/intel/main.bro index 4f7428de18..eba27ca56a 100644 --- a/scripts/base/frameworks/intel/main.bro +++ b/scripts/base/frameworks/intel/main.bro @@ -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`. diff --git a/scripts/base/frameworks/notice/main.bro b/scripts/base/frameworks/notice/main.bro index 4b4a1dcb9e..2418b499e5 100644 --- a/scripts/base/frameworks/notice/main.bro +++ b/scripts/base/frameworks/notice/main.bro @@ -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, diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index 20b05e3600..64fb73fee4 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -2216,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. diff --git a/scripts/base/protocols/ssh/README b/scripts/base/protocols/ssh/README deleted file mode 100644 index c3f68d543f..0000000000 --- a/scripts/base/protocols/ssh/README +++ /dev/null @@ -1 +0,0 @@ -Support for Secure Shell (SSH) protocol analysis. diff --git a/scripts/base/protocols/ssh/__load__.bro b/scripts/base/protocols/ssh/__load__.bro index 0f3cb011f8..7b9b0d9a6c 100644 --- a/scripts/base/protocols/ssh/__load__.bro +++ b/scripts/base/protocols/ssh/__load__.bro @@ -1,3 +1,2 @@ @load ./main - -@load-sigs ./dpd.sig \ No newline at end of file +@load-sigs ./dpd.sig diff --git a/scripts/base/protocols/ssh/dpd.sig b/scripts/base/protocols/ssh/dpd.sig index 95e22908ab..816e7929b3 100644 --- a/scripts/base/protocols/ssh/dpd.sig +++ b/scripts/base/protocols/ssh/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 -} +} \ No newline at end of file diff --git a/scripts/base/protocols/ssh/main.bro b/scripts/base/protocols/ssh/main.bro index 1d97a9d5fc..42c323601c 100644 --- a/scripts/base/protocols/ssh/main.bro +++ b/scripts/base/protocols/ssh/main.bro @@ -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:`SSH::ssh1_server_host_key` and + ## :bro:id:`SSH::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, $path="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); } diff --git a/scripts/policy/frameworks/intel/seen/pubkey-hashes.bro b/scripts/policy/frameworks/intel/seen/pubkey-hashes.bro new file mode 100644 index 0000000000..5301ffb079 --- /dev/null +++ b/scripts/policy/frameworks/intel/seen/pubkey-hashes.bro @@ -0,0 +1,11 @@ +@load base/frameworks/intel +@load ./where-locations + +event ssh_server_host_key(c: connection, hash: string) + { + local seen = Intel::Seen($indicator=hash, + $indicator_type=Intel::PUBKEY_HASH, + $conn=c, + $where=SSH::IN_SERVER_HOST_KEY); + Intel::seen(seen); + } diff --git a/scripts/policy/frameworks/intel/seen/where-locations.bro b/scripts/policy/frameworks/intel/seen/where-locations.bro index b9b4325bc1..f286cc2ff7 100644 --- a/scripts/policy/frameworks/intel/seen/where-locations.bro +++ b/scripts/policy/frameworks/intel/seen/where-locations.bro @@ -21,6 +21,7 @@ export { SMTP::IN_REPLY_TO, SMTP::IN_X_ORIGINATING_IP_HEADER, SMTP::IN_MESSAGE, + SSH::IN_SERVER_HOST_KEY, SSL::IN_SERVER_NAME, SMTP::IN_HEADER, X509::IN_CERT, diff --git a/scripts/policy/protocols/ssh/detect-bruteforcing.bro b/scripts/policy/protocols/ssh/detect-bruteforcing.bro index ba889cbf3c..f0c76ec904 100644 --- a/scripts/policy/protocols/ssh/detect-bruteforcing.bro +++ b/scripts/policy/protocols/ssh/detect-bruteforcing.bro @@ -12,11 +12,11 @@ export { redef enum Notice::Type += { ## Indicates that a host has been identified as crossing the ## :bro:id:`SSH::password_guesses_limit` threshold with - ## heuristically determined failed logins. + ## failed logins. Password_Guessing, ## Indicates that a host previously identified as a "password - ## guesser" has now had a heuristically successful login - ## attempt. This is not currently implemented. + ## guesser" has now had a successful login + ## attempt. This is not currently implemented. Login_By_Password_Guesser, }; @@ -34,8 +34,7 @@ export { const guessing_timeout = 30 mins &redef; ## This value can be used to exclude hosts or entire networks from being - ## tracked as potential "guessers". There are cases where the success - ## heuristic fails and this acts as the whitelist. The index represents + ## tracked as potential "guessers". The index represents ## client subnets and the yield value represents server subnets. const ignore_guessers: table[subnet] of subnet &redef; } @@ -70,7 +69,7 @@ event bro_init() }]); } -event SSH::heuristic_successful_login(c: connection) +event SSH::ssh_auth_successful(c: connection, auth_method_none: bool) { local id = c$id; @@ -79,7 +78,7 @@ event SSH::heuristic_successful_login(c: connection) $where=SSH::SUCCESSFUL_LOGIN]); } -event SSH::heuristic_failed_login(c: connection) +event SSH::ssh_auth_failed(c: connection) { local id = c$id; diff --git a/scripts/policy/protocols/ssh/geo-data.bro b/scripts/policy/protocols/ssh/geo-data.bro index a5fed986ef..00b52058a1 100644 --- a/scripts/policy/protocols/ssh/geo-data.bro +++ b/scripts/policy/protocols/ssh/geo-data.bro @@ -30,7 +30,7 @@ function get_location(c: connection): geo_location return lookup_location(lookup_ip); } -event SSH::heuristic_successful_login(c: connection) &priority=5 +event SSH::ssh_auth_successful(c: connection, auth_method_none: bool) &priority=3 { # Add the location data to the SSH record. c$ssh$remote_location = get_location(c); @@ -45,7 +45,7 @@ event SSH::heuristic_successful_login(c: connection) &priority=5 } } -event SSH::heuristic_failed_login(c: connection) &priority=5 +event SSH::ssh_auth_failed(c: connection) &priority=3 { # Add the location data to the SSH record. c$ssh$remote_location = get_location(c); diff --git a/scripts/policy/protocols/ssh/interesting-hostnames.bro b/scripts/policy/protocols/ssh/interesting-hostnames.bro index f9b3636e62..e43349c030 100644 --- a/scripts/policy/protocols/ssh/interesting-hostnames.bro +++ b/scripts/policy/protocols/ssh/interesting-hostnames.bro @@ -27,7 +27,7 @@ export { /^ftp[0-9]*\./ &redef; } -event SSH::heuristic_successful_login(c: connection) +event SSH::ssh_auth_successful(c: connection, auth_method_none: bool) { for ( host in set(c$id$orig_h, c$id$resp_h) ) { diff --git a/scripts/test-all-policy.bro b/scripts/test-all-policy.bro index dc85986172..a64c725e04 100644 --- a/scripts/test-all-policy.bro +++ b/scripts/test-all-policy.bro @@ -22,6 +22,7 @@ @load frameworks/intel/seen/file-names.bro @load frameworks/intel/seen/http-headers.bro @load frameworks/intel/seen/http-url.bro +@load frameworks/intel/seen/pubkey-hashes.bro @load frameworks/intel/seen/smtp-url-extraction.bro @load frameworks/intel/seen/smtp.bro @load frameworks/intel/seen/ssl.bro diff --git a/src/analyzer/protocol/ssh/CMakeLists.txt b/src/analyzer/protocol/ssh/CMakeLists.txt index 505c89332e..3f82b9561b 100644 --- a/src/analyzer/protocol/ssh/CMakeLists.txt +++ b/src/analyzer/protocol/ssh/CMakeLists.txt @@ -4,6 +4,8 @@ include(BroPlugin) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) bro_plugin_begin(Bro SSH) -bro_plugin_cc(SSH.cc Plugin.cc) -bro_plugin_bif(events.bif) + bro_plugin_cc(SSH.cc Plugin.cc) + bro_plugin_bif(types.bif) + bro_plugin_bif(events.bif) + bro_plugin_pac(ssh.pac ssh-analyzer.pac ssh-protocol.pac) bro_plugin_end() diff --git a/src/analyzer/protocol/ssh/Plugin.cc b/src/analyzer/protocol/ssh/Plugin.cc index 98b4e25522..be5d2f428b 100644 --- a/src/analyzer/protocol/ssh/Plugin.cc +++ b/src/analyzer/protocol/ssh/Plugin.cc @@ -1,25 +1,24 @@ // See the file in the main distribution directory for copyright. - #include "plugin/Plugin.h" - #include "SSH.h" namespace plugin { -namespace Bro_SSH { + namespace Bro_SSH { -class Plugin : public plugin::Plugin { -public: - plugin::Configuration Configure() - { - AddComponent(new ::analyzer::Component("SSH", ::analyzer::ssh::SSH_Analyzer::Instantiate)); + class Plugin : public plugin::Plugin { + public: + plugin::Configuration Configure() + { + AddComponent(new ::analyzer::Component("SSH", ::analyzer::SSH::SSH_Analyzer::Instantiate)); + + plugin::Configuration config; + config.name = "Bro::SSH"; + config.description = "Secure Shell analyzer"; + return config; + } + } plugin; - plugin::Configuration config; - config.name = "Bro::SSH"; - config.description = "SSH analyzer"; - return config; } -} plugin; + } -} -} diff --git a/src/analyzer/protocol/ssh/SSH.cc b/src/analyzer/protocol/ssh/SSH.cc index ab3f6a5e5b..f1f8857e03 100644 --- a/src/analyzer/protocol/ssh/SSH.cc +++ b/src/analyzer/protocol/ssh/SSH.cc @@ -1,105 +1,148 @@ // See the file "COPYING" in the main distribution directory for copyright. -#include "config.h" - -#include - -#include "NetVar.h" #include "SSH.h" -#include "Event.h" -#include "analyzer/protocol/tcp/ContentLine.h" +#include "analyzer/protocol/tcp/TCP_Reassembler.h" + +#include "Reporter.h" + +#include "types.bif.h" #include "events.bif.h" -using namespace analyzer::ssh; +using namespace analyzer::SSH; SSH_Analyzer::SSH_Analyzer(Connection* c) -: tcp::TCP_ApplicationAnalyzer("SSH", c) + : tcp::TCP_ApplicationAnalyzer("SSH", c) { - orig = new tcp::ContentLine_Analyzer(c, true); - orig->SetSkipPartial(true); - orig->SetCRLFAsEOL(LF_as_EOL); - AddSupportAnalyzer(orig); - - resp = new tcp::ContentLine_Analyzer(c, false); - resp->SetSkipPartial(true); - resp->SetCRLFAsEOL(LF_as_EOL); - AddSupportAnalyzer(resp); + interp = new binpac::SSH::SSH_Conn(this); + had_gap = false; + auth_decision_made = false; + skipped_banner = false; + service_accept_size = 0; + userauth_failure_size = 0; } -void SSH_Analyzer::DeliverStream(int length, const u_char* data, bool is_orig) +SSH_Analyzer::~SSH_Analyzer() { - tcp::TCP_ApplicationAnalyzer::DeliverStream(length, data, is_orig); + delete interp; + } - // We're all done processing this endpoint - flag it as such, - // before we even determine whether we have any event generation - // work to do, to make sure we don't do any further work on it. - if ( is_orig ) - orig->SetSkipDeliveries(true); - else - resp->SetSkipDeliveries(true); +void SSH_Analyzer::Done() + { + tcp::TCP_ApplicationAnalyzer::Done(); - if ( TCP() ) + interp->FlowEOF(true); + interp->FlowEOF(false); + } + +void SSH_Analyzer::EndpointEOF(bool is_orig) + { + tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig); + interp->FlowEOF(is_orig); + } + +void SSH_Analyzer::DeliverStream(int len, const u_char* data, bool orig) + { + tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); + + assert(TCP()); + if ( TCP()->IsPartial() ) + return; + + if ( had_gap ) + // If only one side had a content gap, we could still try to + // deliver data to the other side if the script layer can handle this. + return; + + if ( interp->get_state(orig) == binpac::SSH::ENCRYPTED ) { - // Don't try to parse version if there has already been a gap. - tcp::TCP_Endpoint* endp = is_orig ? TCP()->Orig() : TCP()->Resp(); - if ( endp->HadGap() ) + if ( ssh_encrypted_packet ) + BifEvent::generate_ssh_encrypted_packet(interp->bro_analyzer(), interp->bro_analyzer()->Conn(), + orig, len); + + if ( ! auth_decision_made ) + ProcessEncrypted(len, orig); + + return; + } + + try + { + interp->NewData(orig, data, data + len); + } + catch ( const binpac::Exception& e ) + { + ProtocolViolation(fmt("Binpac exception: %s", e.c_msg())); + } + } + +void SSH_Analyzer::Undelivered(uint64 seq, int len, bool orig) + { + tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); + had_gap = true; + interp->NewGap(orig, len); + } + +void SSH_Analyzer::ProcessEncrypted(int len, bool orig) + { + // We're interested in messages from the server for SSH2 + if ( ! orig && (interp->get_version() == binpac::SSH::SSH2) ) + { + // The first thing we see and want to know is the length of + // SSH_MSG_SERVICE_REQUEST, which has a fixed (decrypted) size + // of 24 bytes (17 for content pad-aligned to 8-byte + // boundaries) + if ( ! service_accept_size ) + { + service_accept_size = len; return; - } - - const char* line = (const char*) data; - - // The SSH identification looks like this: - // - // SSH-.-\n - // - // We're interested in the "version" part here. - - if ( length < 4 || memcmp(line, "SSH-", 4) != 0 ) - { - Weird("malformed_ssh_identification"); - ProtocolViolation("malformed ssh identification", line, length); - return; - } - - int i; - for ( i = 4; i < length && line[i] != '-'; ++i ) - ; - - if ( TCP() ) - { - if ( length >= i ) - { - IPAddr dst; - - if ( is_orig ) - dst = TCP()->Orig()->dst_addr; - else - dst = TCP()->Resp()->dst_addr; - - if ( Conn()->VersionFoundEvent(dst, line + i, - length - i) ) - ProtocolConfirmation(); - else - ProtocolViolation("malformed ssh version", - line, length); } - else + + // If our user can authenticate via the "none" method, this + // packet will be a SSH_MSG_USERAUTH_SUCCESS, which has a + // fixed (decrypted) size of 8 bytes (1 for content + // pad-aligned to 8-byte boundaries). relative_len would be + // -16. + if ( ! userauth_failure_size && (len + 16 == service_accept_size) ) { - Weird("malformed_ssh_version"); - ProtocolViolation("malformed ssh version", line, length); + auth_decision_made = true; + if ( ssh_auth_successful ) + BifEvent::generate_ssh_auth_successful(interp->bro_analyzer(), interp->bro_analyzer()->Conn(), true); + return; + } + + // Normally, this packet would be a SSH_MSG_USERAUTH_FAILURE + // message, with a variable length, depending on the + // authentication methods the server supports. If it's too + // big, it might contain a pre-auth MOTD/banner, so we'll just + // skip it. + if ( ! userauth_failure_size ) + { + if ( ! skipped_banner && (len - service_accept_size) > 256 ) + { + skipped_banner = true; + return; + } + userauth_failure_size = len; + return; + } + + // If we've already seen a failure, let's see if this is + // another packet of the same size. + if ( len == userauth_failure_size ) + { + if ( ssh_auth_failed ) + BifEvent::generate_ssh_auth_failed(interp->bro_analyzer(), interp->bro_analyzer()->Conn()); + return; + } + + // ...or a success packet. + if ( len - service_accept_size == -16 ) + { + auth_decision_made = true; + if ( ssh_auth_successful ) + BifEvent::generate_ssh_auth_successful(interp->bro_analyzer(), interp->bro_analyzer()->Conn(), false); + return; } } - - // Generate SSH events. - EventHandlerPtr event = is_orig ? - ssh_client_version : ssh_server_version; - if ( ! event ) - return; - - val_list* vl = new val_list; - vl->append(BuildConnVal()); - vl->append(new StringVal(length, line)); - - ConnectionEvent(event, vl); } diff --git a/src/analyzer/protocol/ssh/SSH.h b/src/analyzer/protocol/ssh/SSH.h index d1f8c36150..dc3a7c5e39 100644 --- a/src/analyzer/protocol/ssh/SSH.h +++ b/src/analyzer/protocol/ssh/SSH.h @@ -3,25 +3,46 @@ #ifndef ANALYZER_PROTOCOL_SSH_SSH_H #define ANALYZER_PROTOCOL_SSH_SSH_H +#include "events.bif.h" + #include "analyzer/protocol/tcp/TCP.h" -#include "analyzer/protocol/tcp/ContentLine.h" +#include "ssh_pac.h" -namespace analyzer { namespace ssh { +namespace analyzer { + namespace SSH { + class SSH_Analyzer : public tcp::TCP_ApplicationAnalyzer { -class SSH_Analyzer : public tcp::TCP_ApplicationAnalyzer { -public: - SSH_Analyzer(Connection* conn); + public: + SSH_Analyzer(Connection* conn); + virtual ~SSH_Analyzer(); - virtual void DeliverStream(int len, const u_char* data, bool orig); + // Overriden from Analyzer. + virtual void Done(); + virtual void DeliverStream(int len, const u_char* data, bool orig); + virtual void Undelivered(uint64 seq, int len, bool orig); - static analyzer::Analyzer* Instantiate(Connection* conn) - { return new SSH_Analyzer(conn); } + // Overriden from tcp::TCP_ApplicationAnalyzer. + virtual void EndpointEOF(bool is_orig); -private: - tcp::ContentLine_Analyzer* orig; - tcp::ContentLine_Analyzer* resp; -}; + static analyzer::Analyzer* Instantiate(Connection* conn) + { return new SSH_Analyzer(conn); } -} } // namespace analyzer::* + protected: + binpac::SSH::SSH_Conn* interp; + void ProcessEncrypted(int len, bool orig); + + bool had_gap; + + // Packet analysis stuff + bool auth_decision_made; + bool skipped_banner; + + int service_accept_size; + int userauth_failure_size; + + }; + + } + } #endif diff --git a/src/analyzer/protocol/ssh/consts.pac b/src/analyzer/protocol/ssh/consts.pac new file mode 100644 index 0000000000..78027f8a51 --- /dev/null +++ b/src/analyzer/protocol/ssh/consts.pac @@ -0,0 +1,126 @@ +enum version { + SSH1 = 1, + SSH2 = 2, + UNK = 3, +}; + +enum state { + VERSION_EXCHANGE = 0, + KEX_INIT = 1, + KEX_DH_GEX = 2, + KEX_DH = 3, + KEX_ECC = 4, + KEX_GSS = 5, + KEX_RSA = 6, + ENCRYPTED = 7, +}; + +# diffie-hellman-group1-sha1 [RFC4253] Section 8.1 +# diffie-hellman-group14-sha1 [RFC4253] Section 8.2 +enum KEX_DH_message_id { + SSH_MSG_KEXDH_INIT = 30, + SSH_MSG_KEXDH_REPLY = 31, +}; + +# diffie-hellman-group-exchange-sha1 [RFC4419] Section 4.1 +# diffie-hellman-group-exchange-sha256 [RFC4419] Section 4.2 +enum KEX_DH_GEX_message_id { + SSH_MSG_KEX_DH_GEX_REQUEST_OLD = 30, + SSH_MSG_KEX_DH_GEX_GROUP = 31, + SSH_MSG_KEX_DH_GEX_INIT = 32, + SSH_MSG_KEX_DH_GEX_REPLY = 33, + SSH_MSG_KEX_DH_GEX_REQUEST = 34, +}; + +# rsa1024-sha1 [RFC4432] +# rsa2048-sha256 [RFC4432] +enum KEX_RSA_message_id { + SSH_MSG_KEXRSA_PUBKEY = 30, + SSH_MSG_KEXRSA_SECRET = 31, + SSH_MSG_KEXRSA_DONE = 32, +}; + +# gss-group1-sha1-* [RFC4462] Section 2.3 +# gss-group14-sha1-* [RFC4462] Section 2.4 +# gss-gex-sha1-* [RFC4462] Section 2.5 +# gss-* [RFC4462] Section 2.6 +enum KEX_GSS_message_id { + SSH_MSG_KEXGSS_INIT = 30, + SSH_MSG_KEXGSS_CONTINUE = 31, + SSH_MSG_KEXGSS_COMPLETE = 32, + SSH_MSG_KEXGSS_HOSTKEY = 33, + SSH_MSG_KEXGSS_ERROR = 34, + SSH_MSG_KEXGSS_GROUPREQ = 40, + SSH_MSG_KEXGSS_GROUP = 41, +}; + +# ecdh-sha2-* [RFC5656] +enum KEX_ECDH_message_id { + SSH_MSG_KEX_ECDH_INIT = 30, + SSH_MSG_KEX_ECDH_REPLY = 31, +}; + +# ecmqv-sha2 [RFC5656] +enum KEX_ECMQV_message_id { + SSH_MSG_ECMQV_INIT = 30, + SSH_MSG_ECMQV_REPLY = 31, +}; + +enum ssh1_message_id { + SSH_MSG_NONE = 0, + SSH_MSG_DISCONNECT = 1, + SSH_SMSG_PUBLIC_KEY = 2, + SSH_CMSG_SESSION_KEY = 3, + SSH_CMSG_USER = 4, + SSH_CMSG_AUTH_RHOSTS = 5, + SSH_CMSG_AUTH_RSA = 6, + SSH_SMSG_AUTH_RSA_CHALLENGE = 7, + SSH_CMSG_AUTH_RSA_RESPONSE = 8, + SSH_CMSG_AUTH_PASSWORD = 9, + SSH_CMSG_REQUEST_PTY = 10, + SSH_CMSG_WINDOW_SIZE = 11, + SSH_CMSG_EXEC_SHELL = 12, + SSH_CMSG_EXEC_CMD = 13, + SSH_SMSG_SUCCESS = 14, + SSH_SMSG_FAILURE = 15, + SSH_CMSG_STDIN_DATA = 16, + SSH_SMSG_STDOUT_DATA = 17, + SSH_SMSG_STDERR_DATA = 18, + SSH_CMSG_EOF = 19, + SSH_SMSG_EXITSTATUS = 20, + SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 21, + SSH_MSG_CHANNEL_OPEN_FAILURE = 22, + SSH_MSG_CHANNEL_DATA = 23, + SSH_MSG_CHANNEL_CLOSE = 24, + SSH_MSG_CHANNEL_CLOSE_CONFIRMATION = 25, + SSH_CMSG_X11_REQUEST_FORWARDING_OLD = 26, + SSH_SMSG_X11_OPEN = 27, + SSH_CMSG_PORT_FORWARD_REQUEST = 28, + SSH_MSG_PORT_OPEN = 29, + SSH_CMSG_AGENT_REQUEST_FORWARDING = 30, + SSH_SMSG_AGENT_OPEN = 31, + SSH_MSG_IGNORE = 32, + SSH_CMSG_EXIT_CONFIRMATION = 33, + SSH_CMSG_X11_REQUEST_FORWARDING = 34, + SSH_CMSG_AUTH_RHOSTS_RSA = 35, + SSH_MSG_DEBUG = 36, + SSH_CMSG_REQUEST_COMPRESSION = 37, + SSH_CMSG_MAX_PACKET_SIZE = 38, + SSH_CMSG_AUTH_TIS = 39, + SSH_SMSG_AUTH_TIS_CHALLENGE = 40, + SSH_CMSG_AUTH_TIS_RESPONSE = 41, + SSH_CMSG_AUTH_KERBEROS = 42, + SSH_SMSG_AUTH_KERBEROS_RESPONSE = 43, + SSH_CMSG_HAVE_KERBEROS_TGT = 44, +}; + +enum ssh2_message_id { + MSG_DISCONNECT = 1, + MSG_IGNORE = 2, + MSG_UNIMPLEMENTED = 3, + MSG_DEBUG = 4, + MSG_SERVICE_REQUEST = 5, + MSG_SERVICE_ACCEPT = 6, + MSG_KEXINIT = 20, + MSG_NEWKEYS = 21, +}; diff --git a/src/analyzer/protocol/ssh/events.bif b/src/analyzer/protocol/ssh/events.bif index 9d73f5e483..57b736ac85 100644 --- a/src/analyzer/protocol/ssh/events.bif +++ b/src/analyzer/protocol/ssh/events.bif @@ -1,38 +1,194 @@ -## Generated when seeing an SSH client's version identification. The SSH -## protocol starts with a clear-text handshake message that reports client and -## server protocol/software versions. This event provides access to what the -## client sent. +## An :abbr:`SSH (Secure Shell)` Protocol Version Exchange message +## from the server. This contains an identification string that's used +## for version identification. See :rfc:`4253#section-4.2` for +## details. ## +## c: The connection over which the message was sent. ## -## See `Wikipedia `__ for more -## information about the SSH protocol. +## version: The identification string ## -## c: The connection. -## -## version: The version string the client sent (e.g., `SSH-2.0-libssh-0.11`). -## -## .. bro:see:: ssh_server_version -## -## .. note:: As everything after the initial version handshake proceeds -## encrypted, Bro cannot further analyze SSH sessions. -event ssh_client_version%(c: connection, version: string%); - -## Generated when seeing an SSH server's version identification. The SSH -## protocol starts with a clear-text handshake message that reports client and -## server protocol/software versions. This event provides access to what the -## server sent. -## -## See `Wikipedia `__ for more -## information about the SSH protocol. -## -## c: The connection. -## -## version: The version string the server sent (e.g., -## ``SSH-1.99-OpenSSH_3.9p1``). -## -## .. bro:see:: ssh_client_version -## -## .. note:: As everything coming after the initial version handshake proceeds -## encrypted, Bro cannot further analyze SSH sessions. +## .. bro:see:: ssh_client_version ssh_auth_successful ssh_auth_failed +## ssh_capabilities ssh2_server_host_key ssh1_server_host_key +## ssh_encrypted_packet ssh2_dh_server_params +## ssh2_gss_error ssh2_ecc_key event ssh_server_version%(c: connection, version: string%); +## An :abbr:`SSH (Secure Shell)` Protocol Version Exchange message +## from the client. This contains an identification string that's used +## for version identification. See :rfc:`4253#section-4.2` for +## details. +## +## c: The connection over which the message was sent. +## +## version: The identification string +## +## .. bro:see:: ssh_server_version ssh_auth_successful ssh_auth_failed +## ssh_capabilities ssh2_server_host_key ssh1_server_host_key +## ssh_encrypted_packet ssh2_dh_server_params +## ssh2_gss_error ssh2_ecc_key +event ssh_client_version%(c: connection, version: string%); + +## This event is generated when an :abbr:`SSH (Secure Shell)` +## connection was determined to have had a successful +## authentication. This determination is based on packet size +## analysis, and errs on the side of caution - that is, if there's any +## doubt about the authentication success, this event is *not* raised. +## +## c: The connection over which the :abbr:`SSH (Secure Shell)` +## connection took place. +## +## auth_method_none: This is true if the analyzer detected a +## successful connection before any authentication challenge. The +## :abbr:`SSH (Secure Shell)` protocol provides a mechanism for +## unauthenticated access, which some servers support. +## +## .. bro:see:: ssh_server_version ssh_client_version ssh_auth_failed +## ssh_capabilities ssh2_server_host_key ssh1_server_host_key +## ssh_encrypted_packet ssh2_dh_server_params +## ssh2_gss_error ssh2_ecc_key +event ssh_auth_successful%(c: connection, auth_method_none: bool%); + +## This event is generated when an :abbr:`SSH (Secure Shell)` +## connection was determined to have had a failed authentication. This +## determination is based on packet size analysis, and errs on the +## side of caution - that is, if there's any doubt about the +## authentication failure, this event is *not* raised. +## +## c: The connection over which the :abbr:`SSH (Secure Shell)` +## connection took place. +## +## .. bro:see:: ssh_server_version ssh_client_version +## ssh_auth_successful ssh_capabilities ssh2_server_host_key +## ssh1_server_host_key ssh_encrypted_packet ssh2_dh_server_params +## ssh2_gss_error ssh2_ecc_key +event ssh_auth_failed%(c: connection%); + +## During the initial :abbr:`SSH (Secure Shell)` key exchange, each +## endpoint lists the algorithms that it supports, in order of +## preference. This event is generated for each endpoint, when the +## SSH_MSG_KEXINIT message is seen. See :rfc:`4253#section-7.1` for +## details. +## +## c: The connection over which the :abbr:`SSH (Secure Shell)` +## connection took place. +## +## cookie: The SSH_MSG_KEXINIT cookie - a random value generated by +## the sender. +## +## capabilities: The list of algorithms and languages that the sender +## advertises support for, in order of preference. +## +## .. bro:see:: ssh_server_version ssh_client_version +## ssh_auth_successful ssh_auth_failed ssh2_server_host_key +## ssh1_server_host_key ssh_encrypted_packet ssh2_dh_server_params +## ssh2_gss_error ssh2_ecc_key +event ssh_capabilities%(c: connection, cookie: string, capabilities: SSH::Capabilities%); + +## During the :abbr:`SSH (Secure Shell)` key exchange, the server +## supplies its public host key. This event is generated when the +## appropriate key exchange message is seen for SSH2. +## +## c: The connection over which the :abbr:`SSH (Secure Shell)` +## connection took place. +## +## key: The server's public host key. Note that this is the public key +## itself, and not just the fingerprint or hash. +## +## .. bro:see:: ssh_server_version ssh_client_version +## ssh_auth_successful ssh_auth_failed ssh_capabilities +## ssh1_server_host_key ssh_encrypted_packet ssh2_dh_server_params +## ssh2_gss_error ssh2_ecc_key +event ssh2_server_host_key%(c: connection, key: string%); + +## During the :abbr:`SSH (Secure Shell)` key exchange, the server +## supplies its public host key. This event is generated when the +## appropriate key exchange message is seen for SSH1. +## +## c: The connection over which the :abbr:`SSH (Secure Shell)` +## connection took place. +## +## p: The prime for the server's public host key. +## +## e: The exponent for the serer's public host key. +## +## .. bro:see:: ssh_server_version ssh_client_version +## ssh_auth_successful ssh_auth_failed ssh_capabilities +## ssh2_server_host_key ssh_encrypted_packet ssh2_dh_server_params +## ssh2_gss_error ssh2_ecc_key +event ssh1_server_host_key%(c: connection, p: string, e: string%); + +## This event is generated when an :abbr:`SSH (Secure Shell)` +## encrypted packet is seen. This event is not handled by default, but +## is provided for heuristic analysis scripts. Note that you have to set +## :bro:id:`SSH::skip_processing_after_detection` to false to use this +## event. This carries a performance penalty. +## +## c: The connection over which the :abbr:`SSH (Secure Shell)` +## connection took place. +## +## orig: Whether the packet was sent by the originator of the TCP +## connection. +## +## len: The length of the :abbr:`SSH (Secure Shell)` payload, in +## bytes. Note that this ignores reassembly, as this is unknown. +## +## .. bro:see:: ssh_server_version ssh_client_version +## ssh_auth_successful ssh_auth_failed ssh_capabilities +## ssh2_server_host_key ssh1_server_host_key ssh2_dh_server_params +## ssh2_gss_error ssh2_ecc_key +event ssh_encrypted_packet%(c: connection, orig: bool, len: count%); + +## Generated if the connection uses a Diffie-Hellman Group Exchange +## key exchange method. This event contains the server DH parameters, +## which are sent in the SSH_MSG_KEY_DH_GEX_GROUP message as defined in +## :rfc:`4419#section-3`. +## +## c: The connection. +## +## p: The DH prime modulus. +## +## q: The DH generator. +## +## .. bro:see:: ssl_dh_server_params ssh_server_version +## ssh_client_version ssh_auth_successful ssh_auth_failed +## ssh_capabilities ssh2_server_host_key ssh1_server_host_key +## ssh_encrypted_packet ssh2_gss_error ssh2_ecc_key +event ssh2_dh_server_params%(c: connection, p: string, q: string%); + +## In the event of a GSS-API error on the server, the server MAY send +## send an error message with some additional details. This event is +## generated when such an error message is seen. For more information, +## see :rfc:`4462#section-2.1`. +## +## c: The connection. +## +## major_status: GSS-API major status code. +## +## minor_status: GSS-API minor status code. +## +## err_msg: Detailed human-readable error message +## +## .. bro:see:: ssh_server_version ssh_client_version +## ssh_auth_successful ssh_auth_failed ssh_capabilities +## ssh2_server_host_key ssh1_server_host_key ssh_encrypted_packet +## ssh2_dh_server_params ssh2_ecc_key +event ssh2_gss_error%(c: connection, major_status: count, minor_status: count, err_msg: string%); + +## The :abbr:`ECDH (Elliptic Curve Diffie-Hellman)` and +## :abbr:`ECMQV (Elliptic Curve Menezes-Qu-Vanstone)` key exchange +## algorithms use two ephemeral key pairs to generate a shared +## secret. This event is generated when either the client's or +## server's ephemeral public key is seen. For more information, see: +## :rfc:`5656#section-4`. +## +## c: The connection +## +## is_orig: Did this message come from the originator? +## +## q: The ephemeral public key +## +## .. bro:see:: ssh_server_version ssh_client_version +## ssh_auth_successful ssh_auth_failed ssh_capabilities +## ssh2_server_host_key ssh1_server_host_key ssh_encrypted_packet +## ssh2_dh_server_params ssh2_gss_error +event ssh2_ecc_key%(c: connection, is_orig: bool, q: string%); diff --git a/src/analyzer/protocol/ssh/ssh-analyzer.pac b/src/analyzer/protocol/ssh/ssh-analyzer.pac new file mode 100644 index 0000000000..598dc869ab --- /dev/null +++ b/src/analyzer/protocol/ssh/ssh-analyzer.pac @@ -0,0 +1,221 @@ +%extern{ +#include +#include +#include +%} + +%header{ +VectorVal* name_list_to_vector(const bytestring nl); +%} + +%code{ +// Copied from IRC_Analyzer::SplitWords +VectorVal* name_list_to_vector(const bytestring nl) + { + VectorVal* vv = new VectorVal(internal_type("string_vec")->AsVectorType()); + + string name_list = std_str(nl); + if ( name_list.size() < 1 ) + return vv; + + unsigned int start = 0; + unsigned int split_pos = 0; + + while ( name_list[start] == ',' ) + { + ++start; + ++split_pos; + } + + string word; + while ( (split_pos = name_list.find(',', start)) < name_list.size() ) + { + word = name_list.substr(start, split_pos - start); + if ( word.size() > 0 && word[0] != ',' ) + vv->Assign(vv->Size(), new StringVal(word)); + + start = split_pos + 1; + } + + // Add line end if needed. + if ( start < name_list.size() ) + { + word = name_list.substr(start, name_list.size() - start); + vv->Assign(vv->Size(), new StringVal(word)); + } + return vv; + } +%} + +refine flow SSH_Flow += { + function proc_ssh_version(msg: SSH_Version): bool + %{ + if ( ssh_client_version && ${msg.is_orig } ) + { + BifEvent::generate_ssh_client_version(connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + bytestring_to_val(${msg.version})); + } + else if ( ssh_server_version ) + { + BifEvent::generate_ssh_server_version(connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + bytestring_to_val(${msg.version})); + } + return true; + %} + + function proc_ssh2_kexinit(msg: SSH2_KEXINIT): bool + %{ + if ( ! ssh_capabilities ) + return false; + + RecordVal* result = new RecordVal(BifType::Record::SSH::Capabilities); + result->Assign(0, name_list_to_vector(${msg.kex_algorithms.val})); + result->Assign(1, name_list_to_vector(${msg.server_host_key_algorithms.val})); + + RecordVal* encryption_algs = new RecordVal(BifType::Record::SSH::Algorithm_Prefs); + encryption_algs->Assign(0, name_list_to_vector(${msg.encryption_algorithms_client_to_server.val})); + encryption_algs->Assign(1, name_list_to_vector(${msg.encryption_algorithms_server_to_client.val})); + result->Assign(2, encryption_algs); + + RecordVal* mac_algs = new RecordVal(BifType::Record::SSH::Algorithm_Prefs); + mac_algs->Assign(0, name_list_to_vector(${msg.mac_algorithms_client_to_server.val})); + mac_algs->Assign(1, name_list_to_vector(${msg.mac_algorithms_server_to_client.val})); + result->Assign(3, mac_algs); + + RecordVal* compression_algs = new RecordVal(BifType::Record::SSH::Algorithm_Prefs); + compression_algs->Assign(0, name_list_to_vector(${msg.compression_algorithms_client_to_server.val})); + compression_algs->Assign(1, name_list_to_vector(${msg.compression_algorithms_server_to_client.val})); + result->Assign(4, compression_algs); + + if ( ${msg.languages_client_to_server.len} || ${msg.languages_server_to_client.len} ) + { + RecordVal* languages = new RecordVal(BifType::Record::SSH::Algorithm_Prefs); + if ( ${msg.languages_client_to_server.len} ) + languages->Assign(0, name_list_to_vector(${msg.languages_client_to_server.val})); + if ( ${msg.languages_server_to_client.len} ) + languages->Assign(1, name_list_to_vector(${msg.languages_server_to_client.val})); + + result->Assign(5, languages); + } + + + result->Assign(6, new Val(${msg.is_orig}, TYPE_BOOL)); + + BifEvent::generate_ssh_capabilities(connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), bytestring_to_val(${msg.cookie}), + result); + + return true; + %} + + + function proc_ssh2_dh_gex_group(msg: SSH2_DH_GEX_GROUP): bool + %{ + if ( ssh2_dh_server_params ) + { + BifEvent::generate_ssh2_dh_server_params(connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + bytestring_to_val(${msg.p.val}), bytestring_to_val(${msg.g.val})); + } + return true; + %} + + function proc_ssh2_ecc_key(q: bytestring, is_orig: bool): bool + %{ + if ( ssh2_ecc_key ) + { + BifEvent::generate_ssh2_ecc_key(connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig, bytestring_to_val(q)); + } + return true; + %} + + function proc_ssh2_gss_error(msg: SSH2_GSS_ERROR): bool + %{ + if ( ssh2_gss_error ) + { + BifEvent::generate_ssh2_gss_error(connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + ${msg.major_status}, ${msg.minor_status}, + bytestring_to_val(${msg.message.val})); + } + return true; + %} + + function proc_ssh2_server_host_key(key: bytestring): bool + %{ + if ( ssh2_server_host_key ) + { + BifEvent::generate_ssh2_server_host_key(connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + bytestring_to_val(${key})); + } + return true; + %} + + function proc_ssh1_server_host_key(p: bytestring, e: bytestring): bool + %{ + if ( ssh1_server_host_key ) + { + BifEvent::generate_ssh1_server_host_key(connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + bytestring_to_val(${p}), + bytestring_to_val(${e})); + } + return true; + %} + + function proc_newkeys(): bool + %{ + connection()->bro_analyzer()->ProtocolConfirmation(); + return true; + %} +}; + +refine typeattr SSH_Version += &let { + proc: bool = $context.flow.proc_ssh_version(this); +}; + +refine typeattr SSH2_KEXINIT += &let { + proc: bool = $context.flow.proc_ssh2_kexinit(this); +}; + +refine typeattr SSH1_Message += &let { + proc_newkeys: bool = $context.flow.proc_newkeys() &if(msg_type == SSH_CMSG_SESSION_KEY); +}; + +refine typeattr SSH2_Message += &let { + proc_newkeys: bool = $context.flow.proc_newkeys() &if(msg_type == MSG_NEWKEYS); +}; + +refine typeattr SSH2_DH_GEX_REPLY += &let { + proc: bool = $context.flow.proc_ssh2_server_host_key(k_s.val); +}; + +refine typeattr SSH2_GSS_HOSTKEY += &let { + proc: bool = $context.flow.proc_ssh2_server_host_key(k_s.val); +}; + +refine typeattr SSH2_GSS_ERROR += &let { + proc: bool = $context.flow.proc_ssh2_gss_error(this); +}; + +refine typeattr SSH2_DH_GEX_GROUP += &let { + proc: bool = $context.flow.proc_ssh2_dh_gex_group(this); +}; + +refine typeattr SSH2_ECC_REPLY += &let { + proc_k: bool = $context.flow.proc_ssh2_server_host_key(k_s.val); + proc_q: bool = $context.flow.proc_ssh2_ecc_key(q_s.val, false); +}; + +refine typeattr SSH2_ECC_INIT += &let { + proc: bool = $context.flow.proc_ssh2_ecc_key(q_c.val, true); +}; + +refine typeattr SSH1_PUBLIC_KEY += &let { + proc: bool = $context.flow.proc_ssh1_server_host_key(host_key_p.val, host_key_e.val); +}; diff --git a/src/analyzer/protocol/ssh/ssh-protocol.pac b/src/analyzer/protocol/ssh/ssh-protocol.pac new file mode 100644 index 0000000000..28b0379999 --- /dev/null +++ b/src/analyzer/protocol/ssh/ssh-protocol.pac @@ -0,0 +1,430 @@ +%include consts.pac + +# Common constructs across SSH1 and SSH2 +######################################## + +# We have 3 basic types of messages: +# +# - SSH_Version messages just have a string with the banner string of the client or server +# - Encrypted messages have no usable data, so we'll just ignore them as best we can. +# - Finally, key exchange messages have a common format. + +type SSH_PDU(is_orig: bool) = case $context.connection.get_state(is_orig) of { + VERSION_EXCHANGE -> version : SSH_Version(is_orig); + ENCRYPTED -> encrypted : bytestring &length=1 &transient; + default -> kex : SSH_Key_Exchange(is_orig); +} &byteorder=bigendian; + +type SSH_Version(is_orig: bool) = record { + version : bytestring &oneline; +} &let { + update_state : bool = $context.connection.update_state(KEX_INIT, is_orig); + update_version : bool = $context.connection.update_version(version, is_orig); +}; + +type SSH_Key_Exchange(is_orig: bool) = case $context.connection.get_version() of { + SSH1 -> ssh1_msg : SSH1_Key_Exchange(is_orig); + SSH2 -> ssh2_msg : SSH2_Key_Exchange(is_orig); +}; + +# SSH1 constructs +################# + +type SSH1_Key_Exchange(is_orig: bool) = record { + packet_length : uint32; + pad_fill : bytestring &length = 8 - (packet_length % 8); + msg_type : uint8; + message : SSH1_Message(is_orig, msg_type, packet_length - 5); + crc : uint32; +} &length = packet_length + 4 + 8 - (packet_length % 8); + +type SSH1_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of { + SSH_SMSG_PUBLIC_KEY -> public_key : SSH1_PUBLIC_KEY(length); + SSH_CMSG_SESSION_KEY -> session_key : SSH1_SESSION_KEY(length); +} &let { + detach : bool=$context.connection.update_state(ENCRYPTED, is_orig); +}; + +type SSH1_PUBLIC_KEY(length: uint32) = record { + cookie : bytestring &length=8; + server_key : uint32; + server_key_p : ssh1_mp_int; + server_key_e : ssh1_mp_int; + host_key : uint32; + host_key_p : ssh1_mp_int; + host_key_e : ssh1_mp_int; + flags : uint32; + supported_ciphers : uint32; + supported_auths : uint32; +} &length=length; + +type SSH1_SESSION_KEY(length: uint32) = record { + cipher : uint8; + cookie : bytestring &length=8; + session_key : ssh1_mp_int; + flags : uint32; +} &length=length; + +type ssh1_mp_int = record { + len : uint16; + val : bytestring &length=(len+7)/8; +}; + + +## SSH2 + +type SSH2_Header(is_orig: bool) = record { + packet_length : uint32; + padding_length : uint8; + msg_type : uint8; +} &let { + payload_length : uint32 = packet_length - padding_length - 2; + detach : bool = $context.connection.update_state(ENCRYPTED, is_orig) &if(msg_type == MSG_NEWKEYS); +}; + +type SSH2_Key_Exchange(is_orig: bool) = record { + header : SSH2_Header(is_orig); + payload : SSH2_Message(is_orig, header.msg_type, header.payload_length); + pad : bytestring &length=header.padding_length; +} &length=header.packet_length + 4; + +type SSH2_Message(is_orig: bool, msg_type: uint8, length: uint32) = case $context.connection.get_state(is_orig) of { + KEX_INIT -> kex : SSH2_KEXINIT(length, is_orig); + KEX_DH_GEX -> kex_dh_gex : SSH2_Key_Exchange_DH_GEX_Message(is_orig, msg_type, length); + KEX_DH -> kex_dh : SSH2_Key_Exchange_DH_Message(is_orig, msg_type, length); + KEX_ECC -> kex_ecc : SSH2_Key_Exchange_ECC_Message(is_orig, msg_type, length); + KEX_GSS -> kex_gss : SSH2_Key_Exchange_GSS_Message(is_orig, msg_type, length); + KEX_RSA -> kex_rsa : SSH2_Key_Exchange_RSA_Message(is_orig, msg_type, length); + default -> unknown : bytestring &length=length; +}; + +type SSH2_KEXINIT(length: uint32, is_orig: bool) = record { + cookie : bytestring &length=16; + kex_algorithms : ssh_string; + server_host_key_algorithms : ssh_string; + encryption_algorithms_client_to_server : ssh_string; + encryption_algorithms_server_to_client : ssh_string; + mac_algorithms_client_to_server : ssh_string; + mac_algorithms_server_to_client : ssh_string; + compression_algorithms_client_to_server : ssh_string; + compression_algorithms_server_to_client : ssh_string; + languages_client_to_server : ssh_string; + languages_server_to_client : ssh_string; + first_kex_packet_follows : uint8; + reserved : uint32; +} &let { + proc_kex : bool= $context.connection.update_kex(kex_algorithms.val, is_orig); +} &length=length; + +# KEX_DH exchanges + +type SSH2_Key_Exchange_DH_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of { + SSH_MSG_KEXDH_INIT -> init : SSH2_DH_GEX_INIT(length); + SSH_MSG_KEXDH_REPLY -> reply : SSH2_DH_GEX_REPLY(length); + default -> unknown: bytestring &length=length &transient; +}; + +# KEX_DH_GEX exchanges + +type SSH2_Key_Exchange_DH_GEX_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of { + SSH_MSG_KEX_DH_GEX_REQUEST_OLD -> request_old : SSH2_DH_GEX_REQUEST_OLD; + SSH_MSG_KEX_DH_GEX_REQUEST -> request : SSH2_DH_GEX_REQUEST; + SSH_MSG_KEX_DH_GEX_GROUP -> group : SSH2_DH_GEX_GROUP(length); + SSH_MSG_KEX_DH_GEX_INIT -> init : SSH2_DH_GEX_INIT(length); + SSH_MSG_KEX_DH_GEX_REPLY -> reply : SSH2_DH_GEX_REPLY(length); + default -> unknown : bytestring &length=length &transient; +}; + +type SSH2_DH_GEX_REQUEST = record { + min : uint32; + n : uint32; + max : uint32; +} &length=12; + +type SSH2_DH_GEX_REQUEST_OLD = record { + n : uint32; +} &length=4; + +type SSH2_DH_GEX_GROUP(length: uint32) = record { + p : ssh_string; + g : ssh_string; +} &length=length; + +type SSH2_DH_GEX_INIT(length: uint32) = record { + e : ssh_string; +} &length=length; + +type SSH2_DH_GEX_REPLY(length: uint32) = record { + k_s : ssh_string; + f : ssh_string; + signature : ssh_string; +} &length=length; + +# KEX_RSA exchanges + +type SSH2_Key_Exchange_RSA_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of { + SSH_MSG_KEXRSA_PUBKEY -> pubkey : SSH2_RSA_PUBKEY(length); + SSH_MSG_KEXRSA_SECRET -> secret : SSH2_RSA_SECRET(length); + SSH_MSG_KEXRSA_DONE -> done : SSH2_RSA_DONE(length); +}; + +type SSH2_RSA_PUBKEY(length: uint32) = record { + k_s : ssh_string; + k_t : ssh_string; +} &length=length; + +type SSH2_RSA_SECRET(length: uint32) = record { + encrypted_payload : ssh_string; +} &length=length; + +type SSH2_RSA_DONE(length: uint32) = record { + signature : ssh_string; +} &length=length; + +# KEX_GSS exchanges + +type SSH2_Key_Exchange_GSS_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of { + SSH_MSG_KEXGSS_INIT -> init : SSH2_GSS_INIT(length); + SSH_MSG_KEXGSS_CONTINUE -> cont : SSH2_GSS_CONTINUE(length); + SSH_MSG_KEXGSS_COMPLETE -> complete : SSH2_GSS_COMPLETE(length); + SSH_MSG_KEXGSS_HOSTKEY -> hostkey : SSH2_GSS_HOSTKEY(length); + SSH_MSG_KEXGSS_ERROR -> error : SSH2_GSS_ERROR(length); + SSH_MSG_KEXGSS_GROUPREQ -> groupreq : SSH2_DH_GEX_REQUEST; + SSH_MSG_KEXGSS_GROUP -> group : SSH2_DH_GEX_GROUP(length); +}; + +type SSH2_GSS_INIT(length: uint32) = record { + output_token : ssh_string; + e : ssh_string; +} &length=length; + +type SSH2_GSS_CONTINUE(length: uint32) = record { + output_token : ssh_string; +} &length=length; + +type SSH2_GSS_COMPLETE(length: uint32) = record { + f : ssh_string; + per_msg_token : ssh_string; + have_token : uint8; + parse_token : case have_token of { + 0 -> no_token : empty; + default -> token : ssh_string; + }; +} &length=length; + +type SSH2_GSS_HOSTKEY(length: uint32) = record { + k_s : ssh_string; +} &length=length; + +type SSH2_GSS_ERROR(length: uint32) = record { + major_status : uint32; + minor_status : uint32; + message : ssh_string; + language : ssh_string; +} &length=length; + +# KEX_ECDH and KEX_ECMQV exchanges + +type SSH2_Key_Exchange_ECC_Message(is_orig: bool, msg_type: uint8, length: uint32) = case msg_type of { + SSH_MSG_KEX_ECDH_INIT -> init : SSH2_ECC_INIT(length); + SSH_MSG_KEX_ECDH_REPLY -> reply : SSH2_ECC_REPLY(length); +}; + +# This deviates from the RFC. SSH_MSG_KEX_ECDH_INIT and +# SSH_MSG_KEX_ECMQV_INIT can be parsed the same way. +type SSH2_ECC_INIT(length: uint32) = record { + q_c : ssh_string; +}; + +# This deviates from the RFC. SSH_MSG_KEX_ECDH_REPLY and +# SSH_MSG_KEX_ECMQV_REPLY can be parsed the same way. +type SSH2_ECC_REPLY(length: uint32) = record { + k_s : ssh_string; + q_s : ssh_string; + signature : ssh_string; +}; + +# Helper types + +type ssh_string = record { + len : uint32; + val : bytestring &length=len; +}; + +type ssh_host_key = record { + len : uint32; + key_type : ssh_string; + key : ssh_string; +} &length=(len + 4); + +## Done with types + +refine connection SSH_Conn += { + %member{ + int state_up_; + int state_down_; + int version_; + + bool kex_orig_; + bool kex_seen_; + bytestring kex_algs_cache_; + bytestring kex_algorithm_; + %} + + %init{ + state_up_ = VERSION_EXCHANGE; + state_down_ = VERSION_EXCHANGE; + version_ = UNK; + + kex_seen_ = false; + kex_orig_ = false; + kex_algs_cache_ = bytestring(); + kex_algorithm_ = bytestring(); + %} + + %cleanup{ + kex_algorithm_.free(); + kex_algs_cache_.free(); + %} + + function get_state(is_orig: bool) : int + %{ + if ( is_orig ) + { + return state_up_; + } + else + { + return state_down_; + } + %} + + function update_state(s: state, is_orig: bool) : bool + %{ + if ( is_orig ) + state_up_ = s; + else + state_down_ = s; + return true; + %} + + function get_version() : int + %{ + return version_; + %} + + function update_version(v: bytestring, is_orig: bool) : bool + %{ + if ( is_orig && ( v.length() >= 4 ) ) + { + if ( v[4] == '2' ) + version_ = SSH2; + if ( v[4] == '1' ) + version_ = SSH1; + } + return true; + %} + + function update_kex_state_if_equal(s: string, to_state: state) : bool + %{ + if ( std_str(kex_algorithm_).compare(s) == 0 ) + { + update_state(to_state, true); + update_state(to_state, false); + return true; + } + return false; + %} + + function update_kex_state_if_startswith(s: string, to_state: state) : bool + %{ + if ( (uint) kex_algorithm_.length() < s.length() ) + return false; + + if ( std_str(kex_algorithm_).substr(0, s.length()).compare(s) == 0 ) + { + update_state(to_state, true); + update_state(to_state, false); + return true; + } + return false; + %} + + function update_kex(algs: bytestring, orig: bool) : bool + %{ + if ( !kex_seen_ ) + { + kex_seen_ = true; + kex_orig_ = orig; + kex_algs_cache_.init(${algs}.data(), ${algs}.length()); + + return false; + } + else if ( kex_orig_ == orig ) + return false; + + VectorVal* client_list = name_list_to_vector(orig ? algs : kex_algs_cache_); + VectorVal* server_list = name_list_to_vector(orig ? kex_algs_cache_ : algs); + + for ( unsigned int i = 0; i < client_list->Size(); ++i ) + { + for ( unsigned int j = 0; j < server_list->Size(); ++j ) + { + if ( *(client_list->Lookup(i)->AsStringVal()->AsString()) == *(server_list->Lookup(j)->AsStringVal()->AsString()) ) + { + kex_algorithm_.init((const uint8 *) client_list->Lookup(i)->AsStringVal()->Bytes(), + client_list->Lookup(i)->AsStringVal()->Len()); + + Unref(client_list); + Unref(server_list); + + // UNTESTED + if ( update_kex_state_if_equal("rsa1024-sha1", KEX_RSA) ) + return true; + // UNTESTED + if ( update_kex_state_if_equal("rsa2048-sha256", KEX_RSA) ) + return true; + + // UNTESTED + if ( update_kex_state_if_equal("diffie-hellman-group1-sha1", KEX_DH) ) + return true; + // UNTESTED + if ( update_kex_state_if_equal("diffie-hellman-group14-sha1", KEX_DH) ) + return true; + + if ( update_kex_state_if_equal("diffie-hellman-group-exchange-sha1", KEX_DH_GEX) ) + return true; + if ( update_kex_state_if_equal("diffie-hellman-group-exchange-sha256", KEX_DH_GEX) ) + return true; + + if ( update_kex_state_if_startswith("gss-group1-sha1-", KEX_GSS) ) + return true; + if ( update_kex_state_if_startswith("gss-group14-sha1-", KEX_GSS) ) + return true; + if ( update_kex_state_if_startswith("gss-gex-sha1-", KEX_GSS) ) + return true; + if ( update_kex_state_if_startswith("gss-", KEX_GSS) ) + return true; + + if ( update_kex_state_if_startswith("ecdh-sha2-", KEX_ECC) ) + return true; + if ( update_kex_state_if_equal("ecmqv-sha2", KEX_ECC) ) + return true; + if ( update_kex_state_if_equal("curve25519-sha256@libssh.org", KEX_ECC) ) + return true; + + + bro_analyzer()->Weird(fmt("ssh_unknown_kex_algorithm=%s", c_str(kex_algorithm_))); + return true; + + } + } + } + + Unref(client_list); + Unref(server_list); + + return true; + %} + +}; diff --git a/src/analyzer/protocol/ssh/ssh.pac b/src/analyzer/protocol/ssh/ssh.pac new file mode 100644 index 0000000000..2358f056da --- /dev/null +++ b/src/analyzer/protocol/ssh/ssh.pac @@ -0,0 +1,33 @@ +# Generated by binpac_quickstart + +# Analyzer for Secure Shell +# - ssh-protocol.pac: describes the SSH protocol messages +# - ssh-analyzer.pac: describes the SSH analyzer code + +%include binpac.pac +%include bro.pac + +%extern{ + #include "types.bif.h" + #include "events.bif.h" +%} + +analyzer SSH withcontext { + connection: SSH_Conn; + flow: SSH_Flow; +}; + +# Our connection consists of two flows, one in each direction. +connection SSH_Conn(bro_analyzer: BroAnalyzer) { + upflow = SSH_Flow(true); + downflow = SSH_Flow(false); +}; + +%include ssh-protocol.pac + +# Now we define the flow: +flow SSH_Flow(is_orig: bool) { + flowunit = SSH_PDU(is_orig) withcontext(connection, this); +}; + +%include ssh-analyzer.pac \ No newline at end of file diff --git a/src/analyzer/protocol/ssh/types.bif b/src/analyzer/protocol/ssh/types.bif new file mode 100644 index 0000000000..0b2b861723 --- /dev/null +++ b/src/analyzer/protocol/ssh/types.bif @@ -0,0 +1,6 @@ +module SSH; + +type Algorithm_Prefs: record; +type Capabilities: record; + +module GLOBAL; \ No newline at end of file diff --git a/testing/btest/Baseline/core.tunnels.gre/ssh.log b/testing/btest/Baseline/core.tunnels.gre/ssh.log index 5b05545bd0..51dac36891 100644 --- a/testing/btest/Baseline/core.tunnels.gre/ssh.log +++ b/testing/btest/Baseline/core.tunnels.gre/ssh.log @@ -3,8 +3,8 @@ #empty_field (empty) #unset_field - #path ssh -#open 2014-01-16-21-51-12 -#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p status direction client server -#types time string addr port addr port string enum string string -1055289978.855137 CsRx2w45OKnoww6xl4 66.59.111.190 40264 172.28.2.3 22 failure INBOUND SSH-2.0-OpenSSH_3.6.1p1 SSH-1.99-OpenSSH_3.1p1 -#close 2014-01-16-21-51-12 +#open 2015-03-17-17-42-58 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version auth_success direction client server cipher_alg mac_alg compression_alg kex_alg host_key_alg host_key +#types time string addr port addr port count bool enum string string string string string string string string +1055289978.855543 CsRx2w45OKnoww6xl4 66.59.111.190 40264 172.28.2.3 22 2 - - SSH-2.0-OpenSSH_3.6.1p1 SSH-1.99-OpenSSH_3.1p1 aes128-cbc hmac-md5 none diffie-hellman-group-exchange-sha1 ssh-rsa 20:7c:e5:96:b0:4e:ce:a4:db:e4:aa:29:e8:90:98:07 +#close 2015-03-17-17-42-59 diff --git a/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log b/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log index 297cd80996..f73963c222 100644 --- a/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log +++ b/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log @@ -94,6 +94,7 @@ scripts/base/init-bare.bro build/scripts/base/bif/plugins/Bro_SMTP.events.bif.bro build/scripts/base/bif/plugins/Bro_SMTP.functions.bif.bro build/scripts/base/bif/plugins/Bro_SOCKS.events.bif.bro + build/scripts/base/bif/plugins/Bro_SSH.types.bif.bro build/scripts/base/bif/plugins/Bro_SSH.events.bif.bro build/scripts/base/bif/plugins/Bro_SSL.events.bif.bro build/scripts/base/bif/plugins/Bro_SteppingStone.events.bif.bro diff --git a/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log b/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log index e48f67c348..12cdf4926a 100644 --- a/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log +++ b/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log @@ -94,6 +94,7 @@ scripts/base/init-bare.bro build/scripts/base/bif/plugins/Bro_SMTP.events.bif.bro build/scripts/base/bif/plugins/Bro_SMTP.functions.bif.bro build/scripts/base/bif/plugins/Bro_SOCKS.events.bif.bro + build/scripts/base/bif/plugins/Bro_SSH.types.bif.bro build/scripts/base/bif/plugins/Bro_SSH.events.bif.bro build/scripts/base/bif/plugins/Bro_SSL.events.bif.bro build/scripts/base/bif/plugins/Bro_SteppingStone.events.bif.bro diff --git a/testing/btest/Baseline/plugins.hooks/output b/testing/btest/Baseline/plugins.hooks/output index 37ec9526ac..5387e74b3f 100644 --- a/testing/btest/Baseline/plugins.hooks/output +++ b/testing/btest/Baseline/plugins.hooks/output @@ -196,7 +196,7 @@ 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) -> 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) -> 0.000000 MetaHookPost CallFunction(Log::__create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -> -0.000000 MetaHookPost CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1427139545.064324, node=bro, filter=ip or not ip, init=T, success=T])) -> +0.000000 MetaHookPost CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1427306548.357084, node=bro, filter=ip or not ip, init=T, success=T])) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Cluster::LOG)) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Communication::LOG)) -> 0.000000 MetaHookPost CallFunction(Log::add_default_filter, , (Conn::LOG)) -> @@ -290,7 +290,7 @@ 0.000000 MetaHookPost CallFunction(Log::create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) -> 0.000000 MetaHookPost CallFunction(Log::create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) -> 0.000000 MetaHookPost CallFunction(Log::create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -> -0.000000 MetaHookPost CallFunction(Log::write, , (PacketFilter::LOG, [ts=1427139545.064324, node=bro, filter=ip or not ip, init=T, success=T])) -> +0.000000 MetaHookPost CallFunction(Log::write, , (PacketFilter::LOG, [ts=1427306548.357084, node=bro, filter=ip or not ip, init=T, success=T])) -> 0.000000 MetaHookPost CallFunction(Notice::want_pp, , ()) -> 0.000000 MetaHookPost CallFunction(PacketFilter::build, , ()) -> 0.000000 MetaHookPost CallFunction(PacketFilter::combine_filters, , (ip or not ip, and, )) -> @@ -378,6 +378,7 @@ 0.000000 MetaHookPost LoadFile(./Bro_SQLiteReader.sqlite.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_SQLiteWriter.sqlite.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_SSH.events.bif.bro) -> -1 +0.000000 MetaHookPost LoadFile(./Bro_SSH.types.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_SSL.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_SteppingStone.events.bif.bro) -> -1 0.000000 MetaHookPost LoadFile(./Bro_Syslog.events.bif.bro) -> -1 @@ -737,7 +738,7 @@ 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) 0.000000 MetaHookPre CallFunction(Log::__create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -0.000000 MetaHookPre CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1427139545.064324, node=bro, filter=ip or not ip, init=T, success=T])) +0.000000 MetaHookPre CallFunction(Log::__write, , (PacketFilter::LOG, [ts=1427306548.357084, node=bro, filter=ip or not ip, init=T, success=T])) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Cluster::LOG)) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Communication::LOG)) 0.000000 MetaHookPre CallFunction(Log::add_default_filter, , (Conn::LOG)) @@ -831,7 +832,7 @@ 0.000000 MetaHookPre CallFunction(Log::create_stream, , (Weird::LOG, [columns=, ev=Weird::log_weird, path=weird])) 0.000000 MetaHookPre CallFunction(Log::create_stream, , (X509::LOG, [columns=, ev=X509::log_x509, path=x509])) 0.000000 MetaHookPre CallFunction(Log::create_stream, , (mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql])) -0.000000 MetaHookPre CallFunction(Log::write, , (PacketFilter::LOG, [ts=1427139545.064324, node=bro, filter=ip or not ip, init=T, success=T])) +0.000000 MetaHookPre CallFunction(Log::write, , (PacketFilter::LOG, [ts=1427306548.357084, node=bro, filter=ip or not ip, init=T, success=T])) 0.000000 MetaHookPre CallFunction(Notice::want_pp, , ()) 0.000000 MetaHookPre CallFunction(PacketFilter::build, , ()) 0.000000 MetaHookPre CallFunction(PacketFilter::combine_filters, , (ip or not ip, and, )) @@ -919,6 +920,7 @@ 0.000000 MetaHookPre LoadFile(./Bro_SQLiteReader.sqlite.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_SQLiteWriter.sqlite.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_SSH.events.bif.bro) +0.000000 MetaHookPre LoadFile(./Bro_SSH.types.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_SSL.events.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_SteppingStone.events.bif.bro) 0.000000 MetaHookPre LoadFile(./Bro_Syslog.events.bif.bro) @@ -1277,7 +1279,7 @@ 0.000000 | HookCallFunction Log::__create_stream(Weird::LOG, [columns=, ev=Weird::log_weird, path=weird]) 0.000000 | HookCallFunction Log::__create_stream(X509::LOG, [columns=, ev=X509::log_x509, path=x509]) 0.000000 | HookCallFunction Log::__create_stream(mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql]) -0.000000 | HookCallFunction Log::__write(PacketFilter::LOG, [ts=1427139545.064324, node=bro, filter=ip or not ip, init=T, success=T]) +0.000000 | HookCallFunction Log::__write(PacketFilter::LOG, [ts=1427306548.357084, node=bro, filter=ip or not ip, init=T, success=T]) 0.000000 | HookCallFunction Log::add_default_filter(Cluster::LOG) 0.000000 | HookCallFunction Log::add_default_filter(Communication::LOG) 0.000000 | HookCallFunction Log::add_default_filter(Conn::LOG) @@ -1371,7 +1373,7 @@ 0.000000 | HookCallFunction Log::create_stream(Weird::LOG, [columns=, ev=Weird::log_weird, path=weird]) 0.000000 | HookCallFunction Log::create_stream(X509::LOG, [columns=, ev=X509::log_x509, path=x509]) 0.000000 | HookCallFunction Log::create_stream(mysql::LOG, [columns=, ev=MySQL::log_mysql, path=mysql]) -0.000000 | HookCallFunction Log::write(PacketFilter::LOG, [ts=1427139545.064324, node=bro, filter=ip or not ip, init=T, success=T]) +0.000000 | HookCallFunction Log::write(PacketFilter::LOG, [ts=1427306548.357084, node=bro, filter=ip or not ip, init=T, success=T]) 0.000000 | HookCallFunction Notice::want_pp() 0.000000 | HookCallFunction PacketFilter::build() 0.000000 | HookCallFunction PacketFilter::combine_filters(ip or not ip, and, ) diff --git a/testing/btest/Baseline/scripts.base.protocols.ssh.basic/ssh.log b/testing/btest/Baseline/scripts.base.protocols.ssh.basic/ssh.log new file mode 100644 index 0000000000..58e8a2f742 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ssh.basic/ssh.log @@ -0,0 +1,31 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ssh +#open 2015-03-17-17-44-34 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version auth_success direction client server cipher_alg mac_alg compression_alg kex_alg host_key_alg host_key +#types time string addr port addr port count bool enum string string string string string string string string +1324071333.793037 CXWv6p3arKYeMETxOg 192.168.1.79 51880 131.159.21.1 22 2 F - SSH-2.0-OpenSSH_5.9 SSH-2.0-OpenSSH_5.8 aes128-ctr hmac-md5 none ecdh-sha2-nistp256 ssh-rsa a7:26:62:3f:75:1f:33:8a:f3:32:90:8b:73:fd:2c:83 +1409516196.413240 CjhGID4nQcgTWjvg4c 10.0.0.18 40184 128.2.6.88 41644 2 T - SSH-2.0-OpenSSH_6.6 SSH-2.0-OpenSSH_5.9p1 Debian-5ubuntu1.1 aes128-ctr hmac-md5 none ecdh-sha2-nistp256 ssh-rsa 8a:8d:55:28:1e:71:04:99:94:43:22:89:e5:ff:e9:03 +1419870189.491788 CCvvfg3TEfuqmmG4bh 192.168.2.1 57189 192.168.2.158 22 2 T - SSH-2.0-OpenSSH_6.2 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 aes128-ctr hmac-md5-etm@openssh.com none diffie-hellman-group-exchange-sha256 ssh-rsa 28:78:65:c1:c3:26:f7:1b:65:6a:44:14:d0:04:8f:b3 +1419870206.112061 CsRx2w45OKnoww6xl4 192.168.2.1 57191 192.168.2.158 22 1 - - SSH-1.5-OpenSSH_6.2 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 - - - - - a1:73:d1:e1:25:72:79:71:56:56:65:ed:81:bf:67:98 +1419996264.344957 CRJuHdVW0XPVINV8a 192.168.2.1 55179 192.168.2.158 2200 2 T - SSH-2.0-OpenSSH_6.2 SSH-2.0-paramiko_1.15.2 aes128-ctr hmac-sha1 none diffie-hellman-group14-sha1 ssh-rsa 60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5 +1420588548.729724 CPbrpk1qSsw6ESzHV4 192.168.2.1 56594 192.168.2.158 22 1 - - SSH-1.5-OpenSSH_5.3 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 - - - - - a1:73:d1:e1:25:72:79:71:56:56:65:ed:81:bf:67:98 +1420590124.886029 C6pKV8GSxOnSLghOa 192.168.2.1 56821 192.168.2.158 22 1 - - SSH-1.5-OpenSSH_6.2 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 - - - - - a1:73:d1:e1:25:72:79:71:56:56:65:ed:81:bf:67:98 +1420590308.781417 CIPOse170MGiRM1Qf4 192.168.2.1 56837 192.168.2.158 22 1 - - SSH-1.5-OpenSSH_6.2 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 - - - - - a1:73:d1:e1:25:72:79:71:56:56:65:ed:81:bf:67:98 +1420590322.682734 C7XEbhP654jzLoe3a 192.168.2.1 56845 192.168.2.158 22 1 - - SSH-1.5-OpenSSH_6.2 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 - - - - - a1:73:d1:e1:25:72:79:71:56:56:65:ed:81:bf:67:98 +1420590636.482870 CJ3xTn1c4Zw9TmAE05 192.168.2.1 56875 192.168.2.158 22 1 - - SSH-1.5-OpenSSH_6.2 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 - - - - - a1:73:d1:e1:25:72:79:71:56:56:65:ed:81:bf:67:98 +1420590659.429753 CMXxB5GvmoxJFXdTa 192.168.2.1 56878 192.168.2.158 22 1 - - SSH-1.5-OpenSSH_6.2 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 - - - - - a1:73:d1:e1:25:72:79:71:56:56:65:ed:81:bf:67:98 +1420591379.658841 Caby8b1slFea8xwSmb 192.168.2.1 56940 192.168.2.158 22 1 - - SSH-1.5-OpenSSH_6.2 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 - - - - - a1:73:d1:e1:25:72:79:71:56:56:65:ed:81:bf:67:98 +1420599430.828624 Che1bq3i2rO3KD1Syg 192.168.2.1 57831 192.168.2.158 22 1 - - SSH-1.5-OpenSSH_6.2 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 - - - - - a1:73:d1:e1:25:72:79:71:56:56:65:ed:81:bf:67:98 +1420851448.317515 C3SfNE4BWaU4aSuwkc 192.168.2.1 59246 192.168.2.158 22 2 T - SSH-2.0-OpenSSH_6.2 SSH-1.99-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2 arcfour256 hmac-md5-etm@openssh.com none diffie-hellman-group-exchange-sha256 ssh-rsa 28:78:65:c1:c3:26:f7:1b:65:6a:44:14:d0:04:8f:b3 +1420860283.083659 CEle3f3zno26fFZkrh 192.168.1.32 41164 128.2.10.238 22 2 T - SSH-2.0-OpenSSH_6.6p1-hpn14v4 SSH-1.99-OpenSSH_3.4+p1+gssapi+OpenSSH_3.7.1buf_fix+2006100301 aes128-cbc hmac-md5 none diffie-hellman-group-exchange-sha1 ssh-rsa 7f:e5:81:92:26:77:05:44:c4:60:fb:cd:89:c8:81:ee +1420860616.459806 CwSkQu4eWZCH7OONC1 192.168.1.32 33910 128.2.13.133 22 2 T - SSH-2.0-OpenSSH_6.6p1-hpn14v4 SSH-2.0-OpenSSH_5.3 aes128-ctr hmac-md5 none diffie-hellman-group-exchange-sha256 ssh-rsa 93:d8:4c:0d:b2:c3:2e:da:b9:c0:67:db:e4:8f:95:04 +1420868281.691929 CfTOmO0HKorjr8Zp7 192.168.1.32 41268 128.2.10.238 22 2 F - SSH-2.0-OpenSSH_6.6 SSH-1.99-OpenSSH_3.4+p1+gssapi+OpenSSH_3.7.1buf_fix+2006100301 aes128-cbc hmac-md5 none diffie-hellman-group-exchange-sha1 ssh-rsa 7f:e5:81:92:26:77:05:44:c4:60:fb:cd:89:c8:81:ee +1420917487.230379 Cab0vO1xNYSS2hJkle 192.168.1.31 52294 192.168.1.32 22 2 T - SSH-2.0-OpenSSH_6.7 SSH-2.0-OpenSSH_6.7 chacha20-poly1305@openssh.com hmac-sha2-512-etm@openssh.com none curve25519-sha256@libssh.org ssh-ed25519 e4:b1:8e:ca:6e:0e:e5:3c:7e:a4:0e:70:34:9d:b2:b1 +1421006072.225176 Cx2FqO23omNawSNrxj 192.168.1.31 51489 192.168.1.32 22 2 T - SSH-2.0-OpenSSH_6.7 SSH-2.0-OpenSSH_6.7 chacha20-poly1305@openssh.com hmac-sha2-512-etm@openssh.com none curve25519-sha256@libssh.org ssh-ed25519 e4:b1:8e:ca:6e:0e:e5:3c:7e:a4:0e:70:34:9d:b2:b1 +1421041177.043845 CkDsfG2YIeWJmXWNWj 192.168.1.32 58641 131.103.20.168 22 2 F - SSH-2.0-OpenSSH_6.7 SSH-2.0-OpenSSH_5.3 aes128-ctr hmac-md5 none diffie-hellman-group-exchange-sha256 ssh-rsa 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40 +1421041299.824707 CUKS0W3HFYOnBqSE5e 192.168.1.32 58646 131.103.20.168 22 2 T - SSH-2.0-OpenSSH_6.7 SSH-2.0-OpenSSH_5.3 aes128-ctr hmac-md5 none diffie-hellman-group-exchange-sha256 ssh-rsa 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40 +1421041526.397714 CRrfvP2lalMAYOCLhj 192.168.1.32 58649 131.103.20.168 22 2 T - SSH-2.0-OpenSSH_6.7 SSH-2.0-OpenSSH_5.3 aes128-ctr hmac-md5 none diffie-hellman-group-exchange-sha256 ssh-rsa 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40 +#close 2015-03-17-17-44-34 diff --git a/testing/btest/Traces/ssh-on-port-80.trace b/testing/btest/Traces/ssh/ssh-on-port-80.trace similarity index 100% rename from testing/btest/Traces/ssh-on-port-80.trace rename to testing/btest/Traces/ssh/ssh-on-port-80.trace diff --git a/testing/btest/Traces/ssh/ssh.trace b/testing/btest/Traces/ssh/ssh.trace new file mode 100644 index 0000000000..54980005e2 Binary files /dev/null and b/testing/btest/Traces/ssh/ssh.trace differ diff --git a/testing/btest/core/leaks/ssh.test b/testing/btest/core/leaks/ssh.test new file mode 100644 index 0000000000..cf6316a231 --- /dev/null +++ b/testing/btest/core/leaks/ssh.test @@ -0,0 +1,10 @@ +# Needs perftools support. +# +# @TEST-REQUIRES: bro --help 2>&1 | grep -q mem-leaks +# +# @TEST-GROUP: leaks +# +# @TEST-EXEC: HEAP_CHECK_DUMP_DIRECTORY=. HEAPCHECK=local btest-bg-run bro bro -b -m -r $TRACES/ssh/ssh.trace %INPUT +# @TEST-EXEC: btest-bg-wait 30 + +@load base/protocols/ssh diff --git a/testing/btest/scripts/base/frameworks/analyzer/register-for-port.bro b/testing/btest/scripts/base/frameworks/analyzer/register-for-port.bro index f3b54177e2..a764cc79c3 100644 --- a/testing/btest/scripts/base/frameworks/analyzer/register-for-port.bro +++ b/testing/btest/scripts/base/frameworks/analyzer/register-for-port.bro @@ -1,8 +1,8 @@ # -# @TEST-EXEC: bro -r ${TRACES}/ssh-on-port-80.trace %INPUT dpd_buffer_size=0; +# @TEST-EXEC: bro -r ${TRACES}/ssh/ssh-on-port-80.trace %INPUT dpd_buffer_size=0; # @TEST-EXEC: cat conn.log | bro-cut service | grep -q ssh # -# @TEST-EXEC: bro -r ${TRACES}/ssh-on-port-80.trace dpd_buffer_size=0; +# @TEST-EXEC: bro -r ${TRACES}/ssh/ssh-on-port-80.trace dpd_buffer_size=0; # @TEST-EXEC: cat conn.log | bro-cut service | grep -vq ssh event bro_init() diff --git a/testing/btest/scripts/base/frameworks/input/stream.bro b/testing/btest/scripts/base/frameworks/input/stream.bro index ed497859aa..75228ee102 100644 --- a/testing/btest/scripts/base/frameworks/input/stream.bro +++ b/testing/btest/scripts/base/frameworks/input/stream.bro @@ -21,6 +21,7 @@ T -43 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz F -43 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY SSH::foo\x0a{ \x0aif (0 < SSH::i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} @TEST-END-FILE +@load base/frameworks/communication # keep network time running @load base/protocols/ssh redef exit_only_after_terminate = T; diff --git a/testing/btest/scripts/base/frameworks/input/twotables.bro b/testing/btest/scripts/base/frameworks/input/twotables.bro index f0bedb2673..0e4436afa2 100644 --- a/testing/btest/scripts/base/frameworks/input/twotables.bro +++ b/testing/btest/scripts/base/frameworks/input/twotables.bro @@ -30,6 +30,7 @@ T -43 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz F -44 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY SSH::foo\x0a{ \x0aif (0 < SSH::i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} @TEST-END-FILE +@load base/frameworks/communication # keep network time running @load base/protocols/ssh redef exit_only_after_terminate = T; diff --git a/testing/btest/scripts/base/protocols/ssh/basic.test b/testing/btest/scripts/base/protocols/ssh/basic.test new file mode 100644 index 0000000000..30e726e1f5 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ssh/basic.test @@ -0,0 +1,4 @@ +# This tests some SSH connections and the output log. + +# @TEST-EXEC: bro -r $TRACES/ssh/ssh.trace %INPUT +# @TEST-EXEC: btest-diff ssh.log \ No newline at end of file