Merge remote-tracking branch 'origin/topic/vladg/kerberos'

* origin/topic/vladg/kerberos: (27 commits)
  Add Kerberos to NEWS.
  Add Kerberos memleak btest.
  Add Kerberos analyzer btest.
  Update baselines for Kerberos analyzer.
  Add known ports to krb/main.bro
  KRB: Clean up krb.log a bit.
  Kerberos: Remove debugging output.
  Kerberos: Fix a memleak.
  Kerberos: A couple small tweaks.
  Kerberos: Fix parsing of the cipher in tickets, and add it to the log.
  Kerberos: A couple more formatting fixes.
  Change krb Info string to success bool
  Clean up formatting.
  Documentation update, and rework events a bit.
  Add support for the SAFE message type.
  Add support for AP_REQ, AP_REP, PRIV, and CRED message types.
  Fix parsing error for KRB_Ticket_Sequence
  Continue clean-up. Some reformatting, removing hard-coded values, documentation, etc.
  Kerberos analyzer updates:   - Split up the (quite length) krb-protocol.pac into krb-protocol, krb-defs, krb-types and krb-padata   - Add some supporting types to get rid of awkward and difficult to read case true/false statements   - Clean up the conversion code in krb-analyzer.pac
  Improve Kerberos DPD and fix a few parse errors.
  ...

BIT-1369 #merged
This commit is contained in:
Robin Sommer 2015-04-20 16:22:19 -07:00
commit 87eb5ef811
37 changed files with 3436 additions and 5 deletions

View file

@ -0,0 +1,3 @@
@load ./main
@load ./files
@load-sigs ./dpd.sig

View file

@ -0,0 +1,99 @@
module KRB;
export {
const error_msg: table[count] of string = {
[0] = "KDC_ERR_NONE",
[1] = "KDC_ERR_NAME_EXP",
[2] = "KDC_ERR_SERVICE_EXP",
[3] = "KDC_ERR_BAD_PVNO",
[4] = "KDC_ERR_C_OLD_MAST_KVNO",
[5] = "KDC_ERR_S_OLD_MAST_KVNO",
[6] = "KDC_ERR_C_PRINCIPAL_UNKNOWN",
[7] = "KDC_ERR_S_PRINCIPAL_UNKNOWN",
[8] = "KDC_ERR_PRINCIPAL_NOT_UNIQUE",
[9] = "KDC_ERR_NULL_KEY",
[10] = "KDC_ERR_CANNOT_POSTDATE",
[11] = "KDC_ERR_NEVER_VALID",
[12] = "KDC_ERR_POLICY",
[13] = "KDC_ERR_BADOPTION",
[14] = "KDC_ERR_ETYPE_NOSUPP",
[15] = "KDC_ERR_SUMTYPE_NOSUPP",
[16] = "KDC_ERR_PADATA_TYPE_NOSUPP",
[17] = "KDC_ERR_TRTYPE_NOSUPP",
[18] = "KDC_ERR_CLIENT_REVOKED",
[19] = "KDC_ERR_SERVICE_REVOKED",
[20] = "KDC_ERR_TGT_REVOKED",
[21] = "KDC_ERR_CLIENT_NOTYET",
[22] = "KDC_ERR_SERVICE_NOTYET",
[23] = "KDC_ERR_KEY_EXPIRED",
[24] = "KDC_ERR_PREAUTH_FAILED",
[25] = "KDC_ERR_PREAUTH_REQUIRED",
[26] = "KDC_ERR_SERVER_NOMATCH",
[27] = "KDC_ERR_MUST_USE_USER2USER",
[28] = "KDC_ERR_PATH_NOT_ACCEPTED",
[29] = "KDC_ERR_SVC_UNAVAILABLE",
[31] = "KRB_AP_ERR_BAD_INTEGRITY",
[32] = "KRB_AP_ERR_TKT_EXPIRED",
[33] = "KRB_AP_ERR_TKT_NYV",
[34] = "KRB_AP_ERR_REPEAT",
[35] = "KRB_AP_ERR_NOT_US",
[36] = "KRB_AP_ERR_BADMATCH",
[37] = "KRB_AP_ERR_SKEW",
[38] = "KRB_AP_ERR_BADADDR",
[39] = "KRB_AP_ERR_BADVERSION",
[40] = "KRB_AP_ERR_MSG_TYPE",
[41] = "KRB_AP_ERR_MODIFIED",
[42] = "KRB_AP_ERR_BADORDER",
[44] = "KRB_AP_ERR_BADKEYVER",
[45] = "KRB_AP_ERR_NOKEY",
[46] = "KRB_AP_ERR_MUT_FAIL",
[47] = "KRB_AP_ERR_BADDIRECTION",
[48] = "KRB_AP_ERR_METHOD",
[49] = "KRB_AP_ERR_BADSEQ",
[50] = "KRB_AP_ERR_INAPP_CKSUM",
[51] = "KRB_AP_PATH_NOT_ACCEPTED",
[52] = "KRB_ERR_RESPONSE_TOO_BIG",
[60] = "KRB_ERR_GENERIC",
[61] = "KRB_ERR_FIELD_TOOLONG",
[62] = "KDC_ERROR_CLIENT_NOT_TRUSTED",
[63] = "KDC_ERROR_KDC_NOT_TRUSTED",
[64] = "KDC_ERROR_INVALID_SIG",
[65] = "KDC_ERR_KEY_TOO_WEAK",
[66] = "KDC_ERR_CERTIFICATE_MISMATCH",
[67] = "KRB_AP_ERR_NO_TGT",
[68] = "KDC_ERR_WRONG_REALM",
[69] = "KRB_AP_ERR_USER_TO_USER_REQUIRED",
[70] = "KDC_ERR_CANT_VERIFY_CERTIFICATE",
[71] = "KDC_ERR_INVALID_CERTIFICATE",
[72] = "KDC_ERR_REVOKED_CERTIFICATE",
[73] = "KDC_ERR_REVOCATION_STATUS_UNKNOWN",
[74] = "KDC_ERR_REVOCATION_STATUS_UNAVAILABLE",
[75] = "KDC_ERR_CLIENT_NAME_MISMATCH",
[76] = "KDC_ERR_KDC_NAME_MISMATCH",
};
const cipher_name: table[count] of string = {
[1] = "des-cbc-crc",
[2] = "des-cbc-md4",
[3] = "des-cbc-md5",
[5] = "des3-cbc-md5",
[7] = "des3-cbc-sha1",
[9] = "dsaWithSHA1-CmsOID",
[10] = "md5WithRSAEncryption-CmsOID",
[11] = "sha1WithRSAEncryption-CmsOID",
[12] = "rc2CBC-EnvOID",
[13] = "rsaEncryption-EnvOID",
[14] = "rsaES-OAEP-ENV-OID",
[15] = "des-ede3-cbc-Env-OID",
[16] = "des3-cbc-sha1-kd",
[17] = "aes128-cts-hmac-sha1-96",
[18] = "aes256-cts-hmac-sha1-96",
[23] = "rc4-hmac",
[24] = "rc4-hmac-exp",
[25] = "camellia128-cts-cmac",
[26] = "camellia256-cts-cmac",
[65] = "subkey-keymaterial",
};
}

View file

@ -0,0 +1,26 @@
# This is the ASN.1 encoded version and message type headers
signature dpd_krb_udp_requests {
ip-proto == udp
payload /(\x6a|\x6c).{1,4}\x30.{1,4}\xa1\x03\x02\x01\x05\xa2\x03\x02\x01/
enable "krb"
}
signature dpd_krb_udp_replies {
ip-proto == udp
payload /(\x6b|\x6d|\x7e).{1,4}\x30.{1,4}\xa0\x03\x02\x01\x05\xa1\x03\x02\x01/
enable "krb"
}
signature dpd_krb_tcp_requests {
ip-proto == tcp
payload /.{4}(\x6a|\x6c).{1,4}\x30.{1,4}\xa1\x03\x02\x01\x05\xa2\x03\x02\x01/
enable "krb_tcp"
}
signature dpd_krb_tcp_replies {
ip-proto == tcp
payload /.{4}(\x6b|\x6d|\x7e).{1,4}\x30.{1,4}\xa0\x03\x02\x01\x05\xa1\x03\x02\x01/
enable "krb_tcp"
}

View file

@ -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_response(c: connection, msg: KDC_Response)
{
fill_in_subjects(c);
}
event krb_tgs_response(c: connection, msg: KDC_Response)
{
fill_in_subjects(c);
}
event connection_state_remove(c: connection)
{
fill_in_subjects(c);
}

View file

@ -0,0 +1,250 @@
##! Implements base functionality for KRB analysis. Generates the krb.log file.
module KRB;
@load ./consts
export {
redef enum Log::ID += { LOG };
type Info: record {
## Timestamp for when the event happened.
ts: time &log;
## Unique ID for the connection.
uid: string &log;
## The connection's 4-tuple of endpoint addresses/ports.
id: conn_id &log;
## Request type - Authentication Service ("AS") or
## Ticket Granting Service ("TGS")
request_type: string &log &optional;
## Client
client: string &log &optional;
## Service
service: string &log;
## Request result
success: bool &log &optional;
## Error code
error_code: count &optional;
## Error message
error_msg: string &log &optional;
## Ticket valid from
from: time &log &optional;
## Ticket valid till
till: time &log &optional;
## Ticket encryption type
cipher: string &log &optional;
## Forwardable ticket requested
forwardable: bool &log &optional;
## Renewable ticket requested
renewable: bool &log &optional;
## We've already logged this
logged: bool &default=F;
};
## The server response error texts which are *not* logged.
const ignored_errors: set[string] = {
# This will significantly increase the noisiness of the log.
# However, one attack is to iterate over principals, looking
# for ones that don't require preauth, and then performn
# an offline attack on that ticket. To detect that attack,
# log NEEDED_PREAUTH.
"NEEDED_PREAUTH",
# This is a more specific version of NEEDED_PREAUTH that's used
# by Windows AD Kerberos.
"Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ",
} &redef;
## Event that can be handled to access the KRB record as it is sent on
## to the logging framework.
global log_krb: event(rec: Info);
}
redef record connection += {
krb: Info &optional;
};
const tcp_ports = { 88/tcp };
const udp_ports = { 88/udp };
redef likely_server_ports += { tcp_ports, udp_ports };
event bro_init() &priority=5
{
Analyzer::register_for_ports(Analyzer::ANALYZER_KRB, udp_ports);
Analyzer::register_for_ports(Analyzer::ANALYZER_KRB_TCP, tcp_ports);
Log::create_stream(KRB::LOG, [$columns=Info, $ev=log_krb]);
}
event krb_error(c: connection, msg: Error_Msg) &priority=5
{
local info: Info;
if ( msg?$error_text && msg$error_text in ignored_errors )
{
if ( c?$krb ) delete c$krb;
return;
}
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 && ( msg?$client_name || msg?$client_realm ) )
info$client = fmt("%s%s", msg?$client_name ? msg$client_name + "/" : "",
msg?$client_realm ? msg$client_realm : "");
info$service = msg$service_name;
info$success = F;
info$error_code = msg$error_code;
if ( msg?$error_text ) info$error_msg = msg$error_text;
else if ( msg$error_code in error_msg ) info$error_msg = error_msg[msg$error_code];
c$krb = info;
}
event krb_error(c: connection, msg: Error_Msg) &priority=-5
{
if ( c?$krb )
{
Log::write(KRB::LOG, c$krb);
c$krb$logged = T;
}
}
event krb_as_request(c: connection, msg: KDC_Request) &priority=5
{
if ( c?$krb && c$krb$logged )
return;
local info: Info;
if ( !c?$krb )
{
info$ts = network_time();
info$uid = c$uid;
info$id = c$id;
}
else
info = c$krb;
info$request_type = "AS";
info$client = fmt("%s/%s", msg$client_name, msg$service_realm);
info$service = msg$service_name;
if ( msg?$from )
info$from = msg$from;
info$till = msg$till;
info$forwardable = msg$kdc_options$forwardable;
info$renewable = msg$kdc_options$renewable;
c$krb = info;
}
event krb_tgs_request(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;
info$request_type = "TGS";
info$service = msg$service_name;
if ( msg?$from ) info$from = msg$from;
info$till = msg$till;
info$forwardable = msg$kdc_options$forwardable;
info$renewable = msg$kdc_options$renewable;
c$krb = info;
}
event krb_as_response(c: connection, msg: KDC_Response) &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$cipher = cipher_name[msg$ticket$cipher];
info$success = T;
c$krb = info;
}
event krb_as_response(c: connection, msg: KDC_Response) &priority=-5
{
Log::write(KRB::LOG, c$krb);
c$krb$logged = T;
}
event krb_tgs_response(c: connection, msg: KDC_Response) &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$cipher = cipher_name[msg$ticket$cipher];
info$success = T;
c$krb = info;
}
event krb_tgs_response(c: connection, msg: KDC_Response) &priority=-5
{
Log::write(KRB::LOG, c$krb);
c$krb$logged = T;
}
event connection_state_remove(c: connection) &priority=-5
{
if ( c?$krb && ! c$krb$logged )
Log::write(KRB::LOG, c$krb);
}