From b8376ca733f18af666aff28988dd8d40fc160faa Mon Sep 17 00:00:00 2001 From: Vlad Grigorescu Date: Tue, 20 Jan 2015 20:43:51 -0500 Subject: [PATCH] Add Kerberos support for PKINIT (x509 cert authentication) --- scripts/base/protocols/krb/__load__.bro | 1 + scripts/base/protocols/krb/files.bro | 142 +++++++++++++++++++++ scripts/base/protocols/krb/main.bro | 98 ++++++++------ src/analyzer/protocol/krb/krb-analyzer.pac | 54 +++++++- src/analyzer/protocol/krb/krb-protocol.pac | 60 +++++++++ 5 files changed, 313 insertions(+), 42 deletions(-) create mode 100644 scripts/base/protocols/krb/files.bro diff --git a/scripts/base/protocols/krb/__load__.bro b/scripts/base/protocols/krb/__load__.bro index 9cfbb7a4c4..99f7163caa 100644 --- a/scripts/base/protocols/krb/__load__.bro +++ b/scripts/base/protocols/krb/__load__.bro @@ -1,2 +1,3 @@ @load ./main +@load ./files @load-sigs ./dpd.sig \ No newline at end of file diff --git a/scripts/base/protocols/krb/files.bro b/scripts/base/protocols/krb/files.bro new file mode 100644 index 0000000000..e8c157416b --- /dev/null +++ b/scripts/base/protocols/krb/files.bro @@ -0,0 +1,142 @@ +@load ./main +@load base/utils/conn-ids +@load base/frameworks/files +@load base/files/x509 + +module KRB; + +export { + redef record Info += { + # Client certificate + client_cert: Files::Info &optional; + # Subject of client certificate, if any + client_cert_subject:string &log &optional; + # File unique ID of client cert, if any + client_cert_fuid: string &log &optional; + + # Server certificate + server_cert: Files::Info &optional; + # Subject of server certificate, if any + server_cert_subject:string &log &optional; + # File unique ID of server cert, if any + server_cert_fuid: string &log &optional; + }; + + ## Default file handle provider for KRB. + global get_file_handle: function(c: connection, is_orig: bool): string; + + ## Default file describer for KRB. + global describe_file: function(f: fa_file): string; +} + +function get_file_handle(c: connection, is_orig: bool): string + { + # Unused. File handles are generated in the analyzer. + return ""; + } + +function describe_file(f: fa_file): string + { + if ( f$source != "KRB_TCP" && f$source != "KRB" ) + return ""; + + if ( ! f?$info || ! f$info?$x509 || ! f$info$x509?$certificate ) + return ""; + + # It is difficult to reliably describe a certificate - especially since + # we do not know when this function is called (hence, if the data structures + # are already populated). + # + # Just return a bit of our connection information and hope that that is good enough. + for ( cid in f$conns ) + { + if ( f$conns[cid]?$krb ) + { + local c = f$conns[cid]; + return cat(c$id$resp_h, ":", c$id$resp_p); + } + } + + return cat("Serial: ", f$info$x509$certificate$serial, " Subject: ", + f$info$x509$certificate$subject, " Issuer: ", + f$info$x509$certificate$issuer); + } + +event bro_init() &priority=5 + { + Files::register_protocol(Analyzer::ANALYZER_KRB_TCP, + [$get_file_handle = KRB::get_file_handle, + $describe = KRB::describe_file]); + + Files::register_protocol(Analyzer::ANALYZER_KRB, + [$get_file_handle = KRB::get_file_handle, + $describe = KRB::describe_file]); + } + +event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5 + { + if ( f$source != "KRB_TCP" && f$source != "KRB" ) + return; + + local info: Info; + + if ( ! c?$krb ) + { + info$ts = network_time(); + info$uid = c$uid; + info$id = c$id; + } + else + info = c$krb; + + if ( is_orig ) + { + info$client_cert = f$info; + info$client_cert_fuid = f$id; + } + else + { + info$server_cert = f$info; + info$server_cert_fuid = f$id; + } + + c$krb = info; + + Files::add_analyzer(f, Files::ANALYZER_X509); + # always calculate hashes. They are not necessary for base scripts + # but very useful for identification, and required for policy scripts + Files::add_analyzer(f, Files::ANALYZER_MD5); + Files::add_analyzer(f, Files::ANALYZER_SHA1); + } + +function fill_in_subjects(c: connection) + { + if ( !c?$krb ) + return; + + if ( c$krb?$client_cert && c$krb$client_cert?$x509 && c$krb$client_cert$x509?$certificate ) + c$krb$client_cert_subject = c$krb$client_cert$x509$certificate$subject; + + if ( c$krb?$server_cert && c$krb$server_cert?$x509 && c$krb$server_cert$x509?$certificate ) + c$krb$server_cert_subject = c$krb$server_cert$x509$certificate$subject; + } + +event krb_error(c: connection, msg: Error_Msg) + { + fill_in_subjects(c); + } + +event krb_as_rep(c: connection, msg: KDC_Reply) + { + fill_in_subjects(c); + } + +event krb_tgs_rep(c: connection, msg: KDC_Reply) + { + fill_in_subjects(c); + } + +event connection_state_remove(c: connection) + { + fill_in_subjects(c); + } diff --git a/scripts/base/protocols/krb/main.bro b/scripts/base/protocols/krb/main.bro index 8294477671..97c210319a 100644 --- a/scripts/base/protocols/krb/main.bro +++ b/scripts/base/protocols/krb/main.bro @@ -63,7 +63,7 @@ export { } &redef; ## Event that can be handled to access the KRB record as it is sent on - ## to the loggin framework. + ## to the logging framework. global log_krb: event(rec: Info); } @@ -81,7 +81,7 @@ event bro_init() &priority=5 Analyzer::register_for_ports(Analyzer::ANALYZER_KRB_TCP, tcp_ports); } -event krb_error(c: connection, msg: Error_Msg) +event krb_error(c: connection, msg: Error_Msg) &priority=5 { local info: Info; @@ -123,21 +123,30 @@ event krb_error(c: connection, msg: Error_Msg) info$error_msg = error_msg[msg$error_code]; } - Log::write(KRB::LOG, info); - info$logged = T; - c$krb = info; } -event krb_as_req(c: connection, msg: KDC_Request) +event krb_error(c: connection, msg: Error_Msg) &priority=-5 + { + Log::write(KRB::LOG, c$krb); + c$krb$logged = T; + } + +event krb_as_req(c: connection, msg: KDC_Request) &priority=5 { if ( c?$krb && c$krb$logged ) return; local info: Info; - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; + + if ( !c?$krb ) + { + info$ts = network_time(); + info$uid = c$uid; + info$id = c$id; + } + else + info = c$krb; info$client = fmt("%s/%s", msg$client_name, msg$service_realm); info$service = msg$service_name; @@ -174,7 +183,7 @@ event krb_as_req(c: connection, msg: KDC_Request) c$krb = info; } -event krb_tgs_req(c: connection, msg: KDC_Request) +event krb_tgs_req(c: connection, msg: KDC_Request) &priority=5 { if ( c?$krb && c$krb$logged ) return; @@ -191,7 +200,40 @@ event krb_tgs_req(c: connection, msg: KDC_Request) c$krb = info; } -event krb_as_rep(c: connection, msg: KDC_Reply) +event krb_as_rep(c: connection, msg: KDC_Reply) &priority=5 + { + local info: Info; + + if ( c?$krb && c$krb$logged ) + return; + + if ( c?$krb ) + info = c$krb; + + if ( ! info?$ts ) + { + info$ts = network_time(); + info$uid = c$uid; + info$id = c$id; + } + + if ( ! info?$client ) + info$client = fmt("%s/%s", msg$client_name, msg$client_realm); + + info$service = msg$ticket$service_name; + info$result = "success"; + + c$krb = info; + } + +event krb_as_rep(c: connection, msg: KDC_Reply) &priority=-5 + { + + Log::write(KRB::LOG, c$krb); + c$krb$logged = T; + } + +event krb_tgs_rep(c: connection, msg: KDC_Reply) &priority=5 { local info: Info; @@ -214,42 +256,16 @@ event krb_as_rep(c: connection, msg: KDC_Reply) info$service = msg$ticket$service_name; info$result = "success"; - Log::write(KRB::LOG, info); - info$logged = T; - c$krb = info; } -event krb_tgs_rep(c: connection, msg: KDC_Reply) +event krb_tgs_rep(c: connection, msg: KDC_Reply) &priority=-5 { - local info: Info; - - if ( c?$krb && c$krb$logged ) - return; - - if ( c?$krb ) - info = c$krb; - - if ( ! info?$ts ) - { - info$ts = network_time(); - info$uid = c$uid; - info$id = c$id; - } - - if ( ! info?$client ) - info$client = fmt("%s/%s", msg$client_name, msg$client_realm); - - info$service = msg$ticket$service_name; - info$result = "success"; - - Log::write(KRB::LOG, info); - info$logged = T; - - c$krb = info; + Log::write(KRB::LOG, c$krb); + c$krb$logged = T; } -event connection_state_remove(c: connection) +event connection_state_remove(c: connection) &priority=-5 { if ( c?$krb && ! c$krb$logged ) Log::write(KRB::LOG, c$krb); diff --git a/src/analyzer/protocol/krb/krb-analyzer.pac b/src/analyzer/protocol/krb/krb-analyzer.pac index 3034c4e696..acf7f5e79d 100644 --- a/src/analyzer/protocol/krb/krb-analyzer.pac +++ b/src/analyzer/protocol/krb/krb-analyzer.pac @@ -1,3 +1,8 @@ +%extern{ +#include "file_analysis/Manager.h" +%} + + %header{ Val* GetTimeFromAsn1(const KRB_Time* atime); Val* GetTimeFromAsn1(StringVal* atime); @@ -132,6 +137,29 @@ refine connection KRB_Conn += { type_val->Assign(0, new Val(${msg.padata.padata_elems[i].data_type}, TYPE_COUNT)); type_val->Assign(1, bytestring_to_val(${msg.padata.padata_elems[i].pa_data_element.pa_pw_salt.encoding.content})); padata->Assign(padata->Size(), type_val); + break; + } + case 16: + { + const bytestring& cert = ${msg.padata.padata_elems[i].pa_data_element.pa_pk_as_req.cert}; + + ODesc common; + common.AddRaw("Analyzer::ANALYZER_KRB"); + common.Add(bro_analyzer()->Conn()->StartTime()); + common.AddRaw("T", 1); + bro_analyzer()->Conn()->IDString(&common); + + ODesc file_handle; + file_handle.Add(common.Description()); + file_handle.Add(0); + + string file_id = file_mgr->HashHandle(file_handle.Description()); + + file_mgr->DataIn(reinterpret_cast(cert.data()), + cert.length(), bro_analyzer()->GetAnalyzerTag(), + bro_analyzer()->Conn(), true, file_id); + file_mgr->EndOfFile(file_id); + break; } default: @@ -292,6 +320,29 @@ refine connection KRB_Conn += { type_val->Assign(0, new Val(${msg.padata.padata_elems[i].data_type}, TYPE_COUNT)); type_val->Assign(1, bytestring_to_val(${msg.padata.padata_elems[i].pa_data_element.pa_pw_salt.encoding.content})); padata->Assign(padata->Size(), type_val); + break; + } + case 17: + { + const bytestring& cert = ${msg.padata.padata_elems[i].pa_data_element.pa_pk_as_rep.cert}; + + ODesc common; + common.AddRaw("Analyzer::ANALYZER_KRB"); + common.Add(bro_analyzer()->Conn()->StartTime()); + common.AddRaw("F", 1); + bro_analyzer()->Conn()->IDString(&common); + + ODesc file_handle; + file_handle.Add(common.Description()); + file_handle.Add(1); + + string file_id = file_mgr->HashHandle(file_handle.Description()); + + file_mgr->DataIn(reinterpret_cast(cert.data()), + cert.length(), bro_analyzer()->GetAnalyzerTag(), + bro_analyzer()->Conn(), false, file_id); + file_mgr->EndOfFile(file_id); + break; } default: @@ -506,4 +557,5 @@ refine typeattr KRB_CRED_MSG += &let { #refine typeattr ASN1EncodingMeta += &let { # proc: bool = $context.connection.debug_asn1_encoding_meta(this); -#}; \ No newline at end of file +#}; + diff --git a/src/analyzer/protocol/krb/krb-protocol.pac b/src/analyzer/protocol/krb/krb-protocol.pac index 212342e423..e30ee1fd91 100644 --- a/src/analyzer/protocol/krb/krb-protocol.pac +++ b/src/analyzer/protocol/krb/krb-protocol.pac @@ -89,9 +89,69 @@ type KRB_PA_Data = record { type KRB_PA_Data_Element(type: int64, length: uint64) = case type of { 1 -> pa_tgs_req : KRB_AP_REQ; 3 -> pa_pw_salt : ASN1OctetString; + 16 -> pa_pk_as_req : KRB_PA_PK_AS_Req &length=length; + 17 -> pa_pk_as_rep : KRB_PA_PK_AS_Rep &length=length; default -> unknown : bytestring &length=length; }; +# Octet string metadata +# -- Sequence metadata +# ---- [0] metadata +# ------ Sequence metadata +# -------- OID +# ---------- [0] metadata +# ------------ Sequence metadata +# -------------- version +# -------------- digestAlgorithms +# -------------- signedData +# -------------- certificates +type KRB_PA_PK_AS_Req = record { + string_meta : ASN1EncodingMeta; + seq_meta1 : ASN1EncodingMeta; + elem_0_meta1: ASN1EncodingMeta; + seq_meta2 : ASN1EncodingMeta; + oid : ASN1Encoding; + elem_0_meta2: ASN1EncodingMeta; + seq_meta3 : ASN1EncodingMeta; + version : ASN1Encoding; + digest_algs : ASN1Encoding; + signed_data : ASN1Encoding; + cert_meta : ASN1EncodingMeta; + cert : bytestring &length=cert_meta.length; + # Ignore everything else + : bytestring &restofdata &transient; +}; + +# Octet string metadata +# -- [0] metadata +# ---- Sequence metadata +# ------ [0] metadata +# -------- Sequence metadata +# ---------- OID +# ------------ [0] metadata +# -------------- Sequence metadata +# ---------------- version +# ---------------- digestAlgorithms +# ---------------- signedData +# ---------------- certificates +type KRB_PA_PK_AS_Rep = record { + string_meta : ASN1EncodingMeta; + elem_0_meta1: ASN1EncodingMeta; + seq_meta1 : ASN1EncodingMeta; + elem_0_meta2: ASN1EncodingMeta; + seq_meta2 : ASN1EncodingMeta; + oid : ASN1Encoding; + elem_0_meta3: ASN1EncodingMeta; + seq_meta3 : ASN1EncodingMeta; + version : ASN1Encoding; + digest_algs : ASN1Encoding; + signed_data : ASN1Encoding; + cert_meta : ASN1EncodingMeta; + cert : bytestring &length=cert_meta.length; + # Ignore everything else + : bytestring &restofdata &transient; +}; + type KRB_REQ_Body = record { seq_meta : ASN1EncodingMeta; args : KRB_REQ_Arg[];