From 574bcb0a51b18d1e209f75f89bbe8ee4b9e6306a Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Tue, 21 Jul 2015 11:57:16 -0700 Subject: [PATCH] Add simple XMPP StartTLS analyzer. This is a very simple XMPP analyzer that basically only can parse the protocol until the client and server start negotiating a TLS session. At that point, the TLS analyzer is attached. While the basic case seems to be working, I fully expect that I missed something and that this might break in a lot of cases. --- scripts/base/init-default.bro | 1 + scripts/base/protocols/xmpp/README | 5 + scripts/base/protocols/xmpp/__load__.bro | 1 + scripts/base/protocols/xmpp/main.bro | 11 +++ src/analyzer/protocol/CMakeLists.txt | 1 + src/analyzer/protocol/xmpp/CMakeLists.txt | 11 +++ src/analyzer/protocol/xmpp/Plugin.cc | 26 ++++++ src/analyzer/protocol/xmpp/XMPP.cc | 87 ++++++++++++++++++ src/analyzer/protocol/xmpp/XMPP.h | 38 ++++++++ src/analyzer/protocol/xmpp/xmpp-analyzer.pac | 41 +++++++++ src/analyzer/protocol/xmpp/xmpp-protocol.pac | 17 ++++ src/analyzer/protocol/xmpp/xmpp.pac | 35 +++++++ .../conn.log | 10 ++ .../ssl.log | 10 ++ .../x509.log | 11 +++ testing/btest/Traces/tls/xmpp-starttls.pcap | Bin 0 -> 8174 bytes .../scripts/base/protocols/xmpp/starttls.test | 9 ++ 17 files changed, 314 insertions(+) create mode 100644 scripts/base/protocols/xmpp/README create mode 100644 scripts/base/protocols/xmpp/__load__.bro create mode 100644 scripts/base/protocols/xmpp/main.bro create mode 100644 src/analyzer/protocol/xmpp/CMakeLists.txt create mode 100644 src/analyzer/protocol/xmpp/Plugin.cc create mode 100644 src/analyzer/protocol/xmpp/XMPP.cc create mode 100644 src/analyzer/protocol/xmpp/XMPP.h create mode 100644 src/analyzer/protocol/xmpp/xmpp-analyzer.pac create mode 100644 src/analyzer/protocol/xmpp/xmpp-protocol.pac create mode 100644 src/analyzer/protocol/xmpp/xmpp.pac create mode 100644 testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/conn.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/ssl.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/x509.log create mode 100644 testing/btest/Traces/tls/xmpp-starttls.pcap create mode 100644 testing/btest/scripts/base/protocols/xmpp/starttls.test diff --git a/scripts/base/init-default.bro b/scripts/base/init-default.bro index 473d94fc84..7e921a6831 100644 --- a/scripts/base/init-default.bro +++ b/scripts/base/init-default.bro @@ -59,6 +59,7 @@ @load base/protocols/ssl @load base/protocols/syslog @load base/protocols/tunnels +@load base/protocols/xmpp @load base/files/pe @load base/files/hash diff --git a/scripts/base/protocols/xmpp/README b/scripts/base/protocols/xmpp/README new file mode 100644 index 0000000000..3d2194ef3d --- /dev/null +++ b/scripts/base/protocols/xmpp/README @@ -0,0 +1,5 @@ +Support for the Extensible Messaging and Presence Protocol (XMPP). + +Note that currently the XMPP analyzer only supports analyzing XMPP sessions +until they do or do not switch to TLS using StartTLS. Hence, we do not get +actual chat information from XMPP sessions, only X509 certificates. diff --git a/scripts/base/protocols/xmpp/__load__.bro b/scripts/base/protocols/xmpp/__load__.bro new file mode 100644 index 0000000000..a10fe855df --- /dev/null +++ b/scripts/base/protocols/xmpp/__load__.bro @@ -0,0 +1 @@ +@load ./main diff --git a/scripts/base/protocols/xmpp/main.bro b/scripts/base/protocols/xmpp/main.bro new file mode 100644 index 0000000000..3d7a4cbc37 --- /dev/null +++ b/scripts/base/protocols/xmpp/main.bro @@ -0,0 +1,11 @@ + +module XMPP; + +const ports = { 5222/tcp, 5269/tcp }; +redef likely_server_ports += { ports }; + +event bro_init() &priority=5 + { + Analyzer::register_for_ports(Analyzer::ANALYZER_XMPP, ports); + } + diff --git a/src/analyzer/protocol/CMakeLists.txt b/src/analyzer/protocol/CMakeLists.txt index 467fce83ee..d19b2ac042 100644 --- a/src/analyzer/protocol/CMakeLists.txt +++ b/src/analyzer/protocol/CMakeLists.txt @@ -43,4 +43,5 @@ add_subdirectory(syslog) add_subdirectory(tcp) add_subdirectory(teredo) add_subdirectory(udp) +add_subdirectory(xmpp) add_subdirectory(zip) diff --git a/src/analyzer/protocol/xmpp/CMakeLists.txt b/src/analyzer/protocol/xmpp/CMakeLists.txt new file mode 100644 index 0000000000..408f01d47c --- /dev/null +++ b/src/analyzer/protocol/xmpp/CMakeLists.txt @@ -0,0 +1,11 @@ + +include(BroPlugin) + +include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +bro_plugin_begin(Bro XMPP) +bro_plugin_cc(Plugin.cc) +bro_plugin_cc(XMPP.cc) +bro_plugin_pac(xmpp.pac xmpp-analyzer.pac xmpp-protocol.pac) +bro_plugin_end() + diff --git a/src/analyzer/protocol/xmpp/Plugin.cc b/src/analyzer/protocol/xmpp/Plugin.cc new file mode 100644 index 0000000000..b4332b447b --- /dev/null +++ b/src/analyzer/protocol/xmpp/Plugin.cc @@ -0,0 +1,26 @@ +// See the file in the main distribution directory for copyright. + + +#include "plugin/Plugin.h" + +#include "XMPP.h" + +namespace plugin { +namespace Bro_XMPP { + +class Plugin : public plugin::Plugin { +public: + plugin::Configuration Configure() + { + AddComponent(new ::analyzer::Component("XMPP", ::analyzer::xmpp::XMPP_Analyzer::Instantiate)); + + + plugin::Configuration config; + config.name = "Bro::XMPP"; + config.description = "XMPP analyzer StartTLS only"; + return config; + } +} plugin; + +} +} diff --git a/src/analyzer/protocol/xmpp/XMPP.cc b/src/analyzer/protocol/xmpp/XMPP.cc new file mode 100644 index 0000000000..c84c372c4d --- /dev/null +++ b/src/analyzer/protocol/xmpp/XMPP.cc @@ -0,0 +1,87 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include "XMPP.h" +#include "analyzer/protocol/tcp/TCP_Reassembler.h" +#include "analyzer/Manager.h" + +using namespace analyzer::xmpp; + +XMPP_Analyzer::XMPP_Analyzer(Connection* conn) + : tcp::TCP_ApplicationAnalyzer("XMPP", conn) + { + interp = new binpac::XMPP::XMPP_Conn(this); + had_gap = false; + tls_active = false; + } + +XMPP_Analyzer::~XMPP_Analyzer() + { + delete interp; + } + +void XMPP_Analyzer::Done() + { + tcp::TCP_ApplicationAnalyzer::Done(); + + interp->FlowEOF(true); + interp->FlowEOF(false); + } + +void XMPP_Analyzer::EndpointEOF(bool is_orig) + { + tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig); + interp->FlowEOF(is_orig); + } + +void XMPP_Analyzer::DeliverStream(int len, const u_char* data, bool orig) + { + tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); + + if ( tls_active ) + { + // If TLS has been initiated, forward to child and abort further + // processing + ForwardStream(len, data, orig); + return; + } + + 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; + + try + { + interp->NewData(orig, data, data + len); + } + catch ( const binpac::Exception& e ) + { + printf("BinPAC Exception: %s\n", e.c_msg()); + ProtocolViolation(fmt("Binpac exception: %s", e.c_msg())); + } + } + +void XMPP_Analyzer::Undelivered(uint64 seq, int len, bool orig) + { + tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); + had_gap = true; + interp->NewGap(orig, len); + } + +void XMPP_Analyzer::StartTLS() + { + // StartTLS was called. This means we saw a client starttls followed + // by a server proceed. From here on, everything should be a binary + // TLS datastream. + + tls_active = true; + + Analyzer* ssl = analyzer_mgr->InstantiateAnalyzer("SSL", Conn()); + if ( ssl ) + AddChildAnalyzer(ssl); + } diff --git a/src/analyzer/protocol/xmpp/XMPP.h b/src/analyzer/protocol/xmpp/XMPP.h new file mode 100644 index 0000000000..628be7bb2d --- /dev/null +++ b/src/analyzer/protocol/xmpp/XMPP.h @@ -0,0 +1,38 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#ifndef ANALYZER_PROTOCOL_XMPP_XMPP_H +#define ANALYZER_PROTOCOL_XMPP_XMPP_H + +#include "analyzer/protocol/tcp/TCP.h" + +#include "xmpp_pac.h" + +namespace analyzer { namespace xmpp { + +class XMPP_Analyzer : public tcp::TCP_ApplicationAnalyzer { +public: + XMPP_Analyzer(Connection* conn); + virtual ~XMPP_Analyzer(); + + virtual void Done(); + virtual void DeliverStream(int len, const u_char* data, bool orig); + virtual void Undelivered(uint64 seq, int len, bool orig); + + // Overriden from tcp::TCP_ApplicationAnalyzer. + virtual void EndpointEOF(bool is_orig); + + void StartTLS(); + + static analyzer::Analyzer* Instantiate(Connection* conn) + { return new XMPP_Analyzer(conn); } + +protected: + binpac::XMPP::XMPP_Conn* interp; + bool had_gap; + + bool tls_active; +}; + +} } // namespace analyzer::* + +#endif /* ANALYZER_PROTOCOL_XMPP_XMPP_H */ diff --git a/src/analyzer/protocol/xmpp/xmpp-analyzer.pac b/src/analyzer/protocol/xmpp/xmpp-analyzer.pac new file mode 100644 index 0000000000..a4417e1601 --- /dev/null +++ b/src/analyzer/protocol/xmpp/xmpp-analyzer.pac @@ -0,0 +1,41 @@ +refine connection XMPP_Conn += { + + %member{ + bool client_starttls; + %} + + %init{ + client_starttls = false; + %} + + function proc_xmpp_token(is_orig: bool, name: bytestring, rest: bytestring): bool + %{ + string token = std_str(name); + + if ( is_orig && token == "stream:stream" ) + // Yup, looks like xmpp... + bro_analyzer()->ProtocolConfirmation(); + + if ( token == "success" || token == "message" ) + // Handshake has passed the phase where we should see StartTLS. Simply skip from hereon... + bro_analyzer()->SetSkip(true); + + if ( is_orig && token == "starttls" ) + client_starttls = true; + + if ( !is_orig && token == "proceed" && client_starttls ) + { + bro_analyzer()->StartTLS(); + } + + //printf("Processed: %d %s %s \n", is_orig, c_str(name), c_str(rest)); + + return true; + %} + +}; + +refine typeattr XMPP_TOKEN += &let { + proc: bool = $context.connection.proc_xmpp_token(is_orig, name, rest); +}; + diff --git a/src/analyzer/protocol/xmpp/xmpp-protocol.pac b/src/analyzer/protocol/xmpp/xmpp-protocol.pac new file mode 100644 index 0000000000..e05268fe32 --- /dev/null +++ b/src/analyzer/protocol/xmpp/xmpp-protocol.pac @@ -0,0 +1,17 @@ +type XML_START = RE//; +type XML_NAME = RE/\/?[?:[:alnum:]]+/; +type XML_REST = RE/[^<>]*/; +type SPACING = RE/[ \r\n]*/; + +type XMPP_PDU(is_orig: bool) = XMPP_TOKEN(is_orig)[] &until($input.length() == 0); + +type XMPP_TOKEN(is_orig: bool) = record { + : SPACING; + : XML_START; + name: XML_NAME; + rest: XML_REST; + : XML_END; + : SPACING; +}; + diff --git a/src/analyzer/protocol/xmpp/xmpp.pac b/src/analyzer/protocol/xmpp/xmpp.pac new file mode 100644 index 0000000000..42ec85f0cc --- /dev/null +++ b/src/analyzer/protocol/xmpp/xmpp.pac @@ -0,0 +1,35 @@ +# binpac file for the XMPP analyzer. +# Note that we currently do not even try to parse the protocol +# completely -- this is only supposed to be able to parse xmpp +# till StartTLS does (or does not) kick in. + +%include binpac.pac +%include bro.pac + +%extern{ +namespace analyzer { namespace xmpp { class XMPP_Analyzer; } } +namespace binpac { namespace XMPP { class XMPP_Conn; } } +typedef analyzer::xmpp::XMPP_Analyzer* XMPPAnalyzer; + +#include "XMPP.h" +%} + +extern type XMPPAnalyzer; + +analyzer XMPP withcontext { + connection: XMPP_Conn; + flow: XMPP_Flow; +}; + +connection XMPP_Conn(bro_analyzer: XMPPAnalyzer) { + upflow = XMPP_Flow(true); + downflow = XMPP_Flow(false); +}; + +%include xmpp-protocol.pac + +flow XMPP_Flow(is_orig: bool) { + datagram = XMPP_PDU(is_orig) withcontext(connection, this); +}; + +%include xmpp-analyzer.pac diff --git a/testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/conn.log b/testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/conn.log new file mode 100644 index 0000000000..2f5bd2f66d --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/conn.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path conn +#open 2015-07-21-18-55-16 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents +#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string] +1437091701.732171 CXWv6p3arKYeMETxOg 198.128.203.95 56048 146.255.57.229 5222 tcp ssl,xmpp 2.213218 676 4678 SF - - 0 ShADadfFr 19 1676 15 5442 (empty) +#close 2015-07-21-18-55-16 diff --git a/testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/ssl.log b/testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/ssl.log new file mode 100644 index 0000000000..f67ea92631 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/ssl.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ssl +#open 2015-07-21-18-55-16 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established cert_chain_fuids client_cert_chain_fuids subject issuer client_subject client_issuer +#types time string addr port addr port string string string string bool string string bool vector[string] vector[string] string string string string +1437091702.232293 CXWv6p3arKYeMETxOg 198.128.203.95 56048 146.255.57.229 5222 TLSv12 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 secp256r1 - F - - T F5Nz2G1vSZQ0QXM2s8,FUw8omi2keRxShDUa (empty) CN=jabber.ccc.de,O=Chaos Computer Club e.V.,L=Hamburg,ST=Hamburg,C=DE emailAddress=support@cacert.org,CN=CA Cert Signing Authority,OU=http://www.cacert.org,O=Root CA - - +#close 2015-07-21-18-55-16 diff --git a/testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/x509.log b/testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/x509.log new file mode 100644 index 0000000000..4a49298e8a --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.xmpp.starttls/x509.log @@ -0,0 +1,11 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path x509 +#open 2015-07-21-18-55-16 +#fields ts id certificate.version certificate.serial certificate.subject certificate.issuer certificate.not_valid_before certificate.not_valid_after certificate.key_alg certificate.sig_alg certificate.key_type certificate.key_length certificate.exponent certificate.curve san.dns san.uri san.email san.ip basic_constraints.ca basic_constraints.path_len +#types time string count string string string time time string string string count string string vector[string] vector[string] vector[string] vector[addr] bool count +1437091702.407347 F5Nz2G1vSZQ0QXM2s8 3 0DF4F2 CN=jabber.ccc.de,O=Chaos Computer Club e.V.,L=Hamburg,ST=Hamburg,C=DE emailAddress=support@cacert.org,CN=CA Cert Signing Authority,OU=http://www.cacert.org,O=Root CA 1382043019.000000 1445115019.000000 rsaEncryption sha1WithRSAEncryption rsa 2048 65537 - jabber.ccc.de,conference.jabber.ccc.de,jabberd.jabber.ccc.de,pubsub.jabber.ccc.de,vjud.jabber.ccc.de - - - F - +1437091702.407347 FUw8omi2keRxShDUa 3 00 emailAddress=support@cacert.org,CN=CA Cert Signing Authority,OU=http://www.cacert.org,O=Root CA emailAddress=support@cacert.org,CN=CA Cert Signing Authority,OU=http://www.cacert.org,O=Root CA 1049027389.000000 1995712189.000000 rsaEncryption md5WithRSAEncryption rsa 4096 65537 - - - - - T - +#close 2015-07-21-18-55-16 diff --git a/testing/btest/Traces/tls/xmpp-starttls.pcap b/testing/btest/Traces/tls/xmpp-starttls.pcap new file mode 100644 index 0000000000000000000000000000000000000000..b4a7ee61e10d771d4783cdc9f369b5d410c60f52 GIT binary patch literal 8174 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