mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
Merge remote-tracking branch 'origin/master' into topic/johanna/spicy-tls
* origin/master: (93 commits) spicyz: Add back message about removed support for port / ports in evt rule-parse: Remove id_to_str() lookup to squelch coverity warning Update doc submodule [nomail] [skip ci] Update zeekctl submodule [nomail] btest: Skip core.script-args under TSAN Update doc submodule [nomail] [skip ci] Update zeekctl submodule Add note to NEWS about the removal of OpaqueVal::DoSerialize and OpaqueVal::DoUnserialize Remove deprecated port/ports fields for spicy analyzers Remove deprecated Cluster::Node::interface field Remove deprecated signature definition format Return an error if GLOBAL:: prefix is used Remove deprecated BloomFilter serialization methods Remove deprecated OpaqueVal serialization methods Remove deprecated DECLARE_OPAQUE_VALUE macro Make TypePtr::Capture member variables private Remove deprecated Trigger constructor Remove deprecated Controller::auto_assign_ports and Controller::auto_assign_start_port Remove deprecated load-balacing policy script Remove deprecated prometheus telemetry policy script ...
This commit is contained in:
commit
1e282989fe
202 changed files with 2903 additions and 1097 deletions
|
@ -1,5 +1,5 @@
|
|||
spicy_add_analyzer(
|
||||
NAME LDAP
|
||||
PACKAGE_NAME spicy-ldap
|
||||
SOURCES ldap.spicy ldap.evt asn1.spicy
|
||||
MODULES LDAP ASN1)
|
||||
SOURCES ldap.spicy ldap.evt asn1.spicy ldap_zeek.spicy
|
||||
MODULES LDAP ASN1 LDAP_Zeek)
|
||||
|
|
|
@ -41,3 +41,18 @@ on LDAP::SearchRequest -> event LDAP::search_request($conn,
|
|||
on LDAP::SearchResultEntry -> event LDAP::search_result_entry($conn,
|
||||
message.messageID,
|
||||
self.objectName);
|
||||
|
||||
on LDAP::ExtendedRequest -> event LDAP::extended_request($conn,
|
||||
message.messageID,
|
||||
self.requestName,
|
||||
self.requestValue);
|
||||
|
||||
on LDAP::ExtendedResponse -> event LDAP::extended_response($conn,
|
||||
message.messageID,
|
||||
message.result_.code,
|
||||
self.responseName,
|
||||
self.responseValue);
|
||||
|
||||
# Once switched into MessageMode::TLS, we won't parse messages anymore,
|
||||
# so this is raised just once.
|
||||
on LDAP::Message if (ctx.messageMode == LDAP::MessageMode::TLS) -> event LDAP::starttls($conn);
|
||||
|
|
|
@ -130,29 +130,104 @@ public type Result = unit {
|
|||
const GSSAPI_MECH_MS_KRB5 = "1.2.840.48018.1.2.2";
|
||||
|
||||
# Supported SASL stripping modes.
|
||||
type SaslStripping = enum {
|
||||
MS_KRB5 = 1, # Payload starts with a 4 byte length followed by a wrap token that may or may not be sealed.
|
||||
type MessageMode = enum {
|
||||
MS_KRB5 = 1, # Payload starts with a 4 byte length followed by a wrap token that may or may not be sealed.
|
||||
TLS = 2, # Client/server used StartTLS, forward to SSL analyzer.
|
||||
MAYBE_ENCRYPTED = 3, # Use a heuristic to determine encrypted traffic.
|
||||
CLEARTEXT = 4, # Assume cleartext.
|
||||
ENCRYPTED = 5, # Assume encrypted.
|
||||
};
|
||||
|
||||
type Ctx = struct {
|
||||
saslStripping: SaslStripping; # Which mode of SASL stripping to use.
|
||||
messageMode: MessageMode; # Message dispatching mode
|
||||
saslMechanism: string; # The SASL mechanism selected by the client.
|
||||
startTlsRequested: bool; # Did the client use the StartTLS extended request?
|
||||
};
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
public type Messages = unit {
|
||||
%context = Ctx;
|
||||
: SASLStrip(self.context())[];
|
||||
: MessageDispatch(self.context())[];
|
||||
};
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
public type SASLStrip = unit(ctx: Ctx&) {
|
||||
switch( ctx.saslStripping ) {
|
||||
SaslStripping::Undef -> : Message(ctx);
|
||||
SaslStripping::MS_KRB5 -> : SaslMsKrb5Stripper(ctx);
|
||||
public type MessageDispatch = unit(ctx: Ctx&) {
|
||||
switch( ctx.messageMode ) {
|
||||
MessageMode::Undef -> : Message(ctx);
|
||||
MessageMode::MS_KRB5 -> : SaslMsKrb5Stripper(ctx);
|
||||
MessageMode::TLS -> : TlsForward; # never returns
|
||||
MessageMode::MAYBE_ENCRYPTED -> : MaybeEncrypted(ctx);
|
||||
MessageMode::CLEARTEXT -> : Message(ctx);
|
||||
MessageMode::ENCRYPTED -> : EncryptedMessage;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
type MaybeEncrypted = unit(ctx: Ctx&) {
|
||||
# A plaintext LDAP message always starts with at least 3 bytes and the first
|
||||
# byte is 0x30 for the sequence. A SASL encrypted message starts with a 4 byte
|
||||
# length field. The heuristic here is that if the first byte is a 0x30,
|
||||
# assume it's unencrypted LDAP. This should be pretty good, if it was an
|
||||
# encrypted/SASL wrapped message, it would have a size between 0x30000000 and
|
||||
# 0x30FFFFFF, meaning at least a size of ~768MB, which seems unlikely.
|
||||
var start: iterator<stream>;
|
||||
var saslLen: uint64;
|
||||
var mech: bytes;
|
||||
|
||||
on %init {
|
||||
self.start = self.input();
|
||||
# Don't have starts_with() on string, work around that.
|
||||
# https://github.com/zeek/spicy/issues/1807
|
||||
self.mech = ctx.saslMechanism.encode(spicy::Charset::UTF8);
|
||||
}
|
||||
|
||||
first: uint8 {
|
||||
if ( $$ == 0x30 ) {
|
||||
ctx.messageMode = MessageMode::CLEARTEXT;
|
||||
} else {
|
||||
ctx.messageMode = MessageMode::ENCRYPTED;
|
||||
}
|
||||
}
|
||||
|
||||
# As a further heuristic, if encrypted mode was decided and the client
|
||||
# requested GSSAPI or GSS-SPNEGO (or we just didn't see it) peak a bit
|
||||
# into the SASL payload and check if it starts with a 0504 (WRAP_TOKEN).
|
||||
# If so, switch into KRB mode assuming that's what is being used and
|
||||
# have a chance seeing some more plaintext LDAP in non-sealed tokens.
|
||||
rem: uint8[3] if ( ctx.messageMode == MessageMode::ENCRYPTED && (|self.mech| == 0 || self.mech.starts_with(b"GSS")) ) {
|
||||
self.saslLen = (uint64(self.first) << 24) + (uint64($$[0]) << 16) + (uint64($$[1]) << 8) + uint64($$[2]);
|
||||
}
|
||||
|
||||
: uint16 if ( self.saslLen >= 2 ) {
|
||||
if ( $$ == 0x0504 ) {
|
||||
ctx.messageMode = MessageMode::MS_KRB5;
|
||||
}
|
||||
}
|
||||
|
||||
# Rewind the input.
|
||||
: void {
|
||||
# Prevent MessageDispatch from recursing endlessly.
|
||||
assert ctx.messageMode != MessageMode::MAYBE_ENCRYPTED;
|
||||
self.set_input(self.start);
|
||||
}
|
||||
|
||||
# One recursion to parse with the new ctx.messageMode setting.
|
||||
: MessageDispatch(ctx);
|
||||
};
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
type EncryptedMessage = unit {
|
||||
len: uint32;
|
||||
: skip bytes &size=self.len;
|
||||
};
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
type TlsForward = unit {
|
||||
# Just consume everything. This is hooked in ldap_zeek.spicy
|
||||
chunk: bytes &chunked &eod;
|
||||
};
|
||||
|
||||
type KrbWrapToken = unit {
|
||||
# https://datatracker.ietf.org/doc/html/rfc4121#section-4.2.6.2
|
||||
|
||||
|
@ -174,7 +249,10 @@ type KrbWrapToken = unit {
|
|||
} else if ( self.rrc == 0 ) {
|
||||
self.trailer_ec = self.ec;
|
||||
} else {
|
||||
throw "Unhandled rc %s and ec %s" % (self.ec, self.rrc);
|
||||
if ( ! self.ctx_flags.sealed )
|
||||
# If it's sealed, we'll consume until &eod anyhow
|
||||
# and ec/rrc shouldn't apply, otherwise, bail.
|
||||
throw "Unhandled rc %s and ec %s" % (self.ec, self.rrc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,6 +301,7 @@ public type Message = unit(ctx: Ctx&) {
|
|||
var arg: string = "";
|
||||
var seqHeaderLen: uint64;
|
||||
var msgLen: uint64;
|
||||
var opLen: uint64;
|
||||
|
||||
seqHeader: ASN1::ASN1Header &requires=($$.tag.class == ASN1::ASN1Class::Universal && $$.tag.type_ == ASN1::ASN1Type::Sequence) {
|
||||
self.msgLen = $$.len.len;
|
||||
|
@ -241,10 +320,11 @@ public type Message = unit(ctx: Ctx&) {
|
|||
|
||||
protocolOp: ASN1::ASN1Header &requires=($$.tag.class == ASN1::ASN1Class::Application) {
|
||||
self.opcode = cast<ProtocolOpcode>(cast<uint8>($$.tag.type_));
|
||||
self.opLen = $$.len.len;
|
||||
}
|
||||
|
||||
switch ( self.opcode ) {
|
||||
ProtocolOpcode::BIND_REQUEST -> BIND_REQUEST: BindRequest(self);
|
||||
ProtocolOpcode::BIND_REQUEST -> BIND_REQUEST: BindRequest(self, ctx);
|
||||
ProtocolOpcode::BIND_RESPONSE -> BIND_RESPONSE: BindResponse(self, ctx);
|
||||
ProtocolOpcode::UNBIND_REQUEST -> UNBIND_REQUEST: UnbindRequest(self);
|
||||
ProtocolOpcode::SEARCH_REQUEST -> SEARCH_REQUEST: SearchRequest(self);
|
||||
|
@ -263,12 +343,12 @@ public type Message = unit(ctx: Ctx&) {
|
|||
# just commenting this out, it will stop processing LDAP Messages in this connection
|
||||
ProtocolOpcode::ADD_REQUEST -> ADD_REQUEST: NotImplemented(self);
|
||||
ProtocolOpcode::COMPARE_REQUEST -> COMPARE_REQUEST: NotImplemented(self);
|
||||
ProtocolOpcode::EXTENDED_REQUEST -> EXTENDED_REQUEST: NotImplemented(self);
|
||||
ProtocolOpcode::EXTENDED_RESPONSE -> EXTENDED_RESPONSE: NotImplemented(self);
|
||||
ProtocolOpcode::EXTENDED_REQUEST -> EXTENDED_REQUEST: ExtendedRequest(self, ctx);
|
||||
ProtocolOpcode::EXTENDED_RESPONSE -> EXTENDED_RESPONSE: ExtendedResponse(self, ctx);
|
||||
ProtocolOpcode::INTERMEDIATE_RESPONSE -> INTERMEDIATE_RESPONSE: NotImplemented(self);
|
||||
ProtocolOpcode::MOD_DN_REQUEST -> MOD_DN_REQUEST: NotImplemented(self);
|
||||
ProtocolOpcode::SEARCH_RESULT_REFERENCE -> SEARCH_RESULT_REFERENCE: NotImplemented(self);
|
||||
} &size=self.protocolOp.len.len;
|
||||
} &size=self.opLen;
|
||||
|
||||
# Ensure some invariants hold after parsing the command.
|
||||
: void &requires=(self.offset() >= self.seqHeaderLen);
|
||||
|
@ -295,26 +375,29 @@ type GSS_SPNEGO_negTokenInit = unit {
|
|||
: skip bytes &eod;
|
||||
};
|
||||
|
||||
# Peak into GSS-SPNEGO payload and ensure it is indeed GSS-SPNEGO.
|
||||
type GSS_SPNEGO = unit {
|
||||
# Peak into GSS-SPNEGO payload and ensure it is indeed GSS-SPNEGO,
|
||||
# or GSS-SPNEGO with a NTMLSSP payload that starts with NTLMSSP.
|
||||
type GSS_SPNEGO_Init = unit {
|
||||
# This is the optional octet string in SaslCredentials.
|
||||
credentialsHeader: ASN1::ASN1Header &requires=($$.tag.type_ == ASN1::ASN1Type::OctetString);
|
||||
|
||||
# Now we either have the initial message as specified in RFC2743 or
|
||||
# a continuation from RFC4178
|
||||
# a continuation from RFC4178, or a "NTMLSSP" signature.
|
||||
#
|
||||
# 60 -> APPLICATION [0] https://datatracker.ietf.org/doc/html/rfc2743#page-81)
|
||||
# 60 -> APPLICATION [0] https://datatracker.ietf.org/doc/html/rfc2743#page-81
|
||||
# a1 -> CHOICE [1] https://www.rfc-editor.org/rfc/rfc4178#section-4.2
|
||||
# "NTMLSSP" https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/907f519d-6217-45b1-b421-dca10fc8af0d
|
||||
#
|
||||
gssapiHeader: ASN1::ASN1Header &requires=(
|
||||
$$.tag.class == ASN1::ASN1Class::Application && $$.tag.type_ == ASN1::ASN1Type(0)
|
||||
|| $$.tag.class == ASN1::ASN1Class::ContextSpecific && $$.tag.type_ == ASN1::ASN1Type(1)
|
||||
);
|
||||
switch {
|
||||
-> spnegoInitByte: uint8(0x60);
|
||||
-> spnegoChoiceByte: uint8(0xa1);
|
||||
-> ntlmSignature: skip b"NTLMSSP"; # Unsupported, should forward to child analyzer!
|
||||
};
|
||||
|
||||
switch ( self.gssapiHeader.tag.type_ ) {
|
||||
ASN1::ASN1Type(0) -> initial: GSS_SPNEGO_negTokenInit;
|
||||
* -> : skip bytes &eod;
|
||||
} &size=self.gssapiHeader.len.len;
|
||||
spnegoLen: skip ASN1::LengthType if (self?.spnegoInitByte || self?.spnegoChoiceByte);
|
||||
|
||||
# Peak into the SPNEGO_negTokenInit
|
||||
spnegoInitial: skip GSS_SPNEGO_negTokenInit if (self?.spnegoInitByte);
|
||||
};
|
||||
|
||||
type SaslCredentials = unit() {
|
||||
|
@ -322,12 +405,22 @@ type SaslCredentials = unit() {
|
|||
|
||||
# Peak into GSS-SPNEGO payload if we have any.
|
||||
switch ( self.mechanism ) {
|
||||
"GSS-SPNEGO" -> gss_spnego: GSS_SPNEGO;
|
||||
"GSS-SPNEGO" -> gss_spnego: GSS_SPNEGO_Init;
|
||||
* -> : skip bytes &eod;
|
||||
};
|
||||
};
|
||||
|
||||
type NegTokenResp = unit {
|
||||
type GSS_SPNEGO_Subsequent = unit {
|
||||
switch {
|
||||
-> spnegoChoiceByte: uint8(0xa1);
|
||||
-> ntmlSignature: skip b"NTLMSSP"; # Unsupported, should forward to NTLM!
|
||||
};
|
||||
|
||||
spnegoChoiceLen: skip ASN1::LengthType if (self?.spnegoChoiceByte);
|
||||
negTokenResp: GSS_SPNEGO_negTokenResp if (self?.spnegoChoiceByte);
|
||||
};
|
||||
|
||||
type GSS_SPNEGO_negTokenResp = unit {
|
||||
var accepted: bool;
|
||||
var supportedMech: ASN1::ASN1Message;
|
||||
|
||||
|
@ -355,34 +448,13 @@ type NegTokenResp = unit {
|
|||
} &parse-from=self.supportedMech.application_data;
|
||||
};
|
||||
|
||||
type ServerSaslCreds = unit {
|
||||
serverSaslCreds: ASN1::ASN1Header &requires=($$.tag.class == ASN1::ASN1Class::ContextSpecific && $$.tag.type_ == ASN1::ASN1Type(7));
|
||||
|
||||
# The PCAP missing_ldap_logs.pcapng has a1 81 b6 here for the GSS-SPNEGO response.
|
||||
#
|
||||
# This is context-specific ID 1, constructed, and a length of 182 as
|
||||
# specified by in 4.2 of RFC4178.
|
||||
#
|
||||
# https://www.rfc-editor.org/rfc/rfc4178#section-4.2
|
||||
#
|
||||
# TODO: This is only valid for a GSS-SPNEGO negTokenResp.
|
||||
# If you want to support something else, remove the requires
|
||||
# and add more to the switch below.
|
||||
choice: ASN1::ASN1Header &requires=($$.tag.class == ASN1::ASN1Class::ContextSpecific);
|
||||
|
||||
switch ( self.choice.tag.type_ ) {
|
||||
ASN1::ASN1Type(1) -> negTokenResp: NegTokenResp;
|
||||
# ...
|
||||
} &size=self.choice.len.len;
|
||||
};
|
||||
|
||||
# TODO(fox-ds): A helper unit for requests for which no handling has been implemented.
|
||||
# Eventually all uses of this unit should be replaced with actual parsers so this unit can be removed.
|
||||
type NotImplemented = unit(inout message: Message) {
|
||||
: skip bytes &eod;
|
||||
};
|
||||
|
||||
type BindRequest = unit(inout message: Message) {
|
||||
type BindRequest = unit(inout message: Message, ctx: Ctx&) {
|
||||
version: ASN1::ASN1Message(True) &convert=$$.body.num_value;
|
||||
name: ASN1::ASN1Message(True) &convert=$$.body.str_value {
|
||||
message.obj = self.name;
|
||||
|
@ -406,12 +478,32 @@ type BindRequest = unit(inout message: Message) {
|
|||
saslCreds: SaslCredentials() &parse-from=self.authData if ((self.authType == BindAuthType::BIND_AUTH_SASL) &&
|
||||
(|self.authData| > 0)) {
|
||||
message.arg = self.saslCreds.mechanism;
|
||||
ctx.saslMechanism = self.saslCreds.mechanism;
|
||||
}
|
||||
} &requires=(self?.authType && (self.authType != BindAuthType::Undef));
|
||||
|
||||
type ServerSaslCreds = unit {
|
||||
serverSaslCreds: ASN1::ASN1Header &requires=($$.tag.class == ASN1::ASN1Class::ContextSpecific && $$.tag.type_ == ASN1::ASN1Type(7));
|
||||
payload: bytes &size=self.serverSaslCreds.len.len;
|
||||
};
|
||||
|
||||
type BindResponse = unit(inout message: Message, ctx: Ctx&) {
|
||||
: Result {
|
||||
message.result_ = $$;
|
||||
|
||||
# The SASL authentication was successful. We do not actually
|
||||
# know if the following messages are encrypted or not. This may be
|
||||
# mechanism and parameter specific. For example SCRAM-SHA512 or NTLM
|
||||
# will continue to be cleartext, while SRP or GSS-API would be encrypted.
|
||||
#
|
||||
# Switch messageMode into trial mode which is explored via MessageDispatch
|
||||
# and the MaybeEncrypted unit.
|
||||
#
|
||||
# Note, messageMode may be changed to something more specific like
|
||||
# MS_KRB5 below.
|
||||
if ( |ctx.saslMechanism| > 0 && $$.code == ResultCode::SUCCESS ) {
|
||||
ctx.messageMode = MessageMode::MAYBE_ENCRYPTED;
|
||||
}
|
||||
}
|
||||
|
||||
# Try to parse serverSaslCreds if there's any input remaining. This
|
||||
|
@ -421,14 +513,18 @@ type BindResponse = unit(inout message: Message, ctx: Ctx&) {
|
|||
# if the serverSaslCreds field exists or not. But, not sure we can
|
||||
# check if there's any bytes left at this point outside of passing
|
||||
# in the length and playing with offset().
|
||||
serverSaslCreds: ServerSaslCreds[] &eod {
|
||||
if ( |self.serverSaslCreds| > 0 ) {
|
||||
if ( self.serverSaslCreds[0]?.negTokenResp ) {
|
||||
local token = self.serverSaslCreds[0].negTokenResp;
|
||||
if ( token.accepted && token?.supportedMechOid ) {
|
||||
if ( token.supportedMechOid == GSSAPI_MECH_MS_KRB5 ) {
|
||||
ctx.saslStripping = SaslStripping::MS_KRB5;
|
||||
}
|
||||
serverSaslCreds: ServerSaslCreds[] &eod;
|
||||
|
||||
# If the client requested GSS-SPNEGO, try to parse the server's response
|
||||
# to switch message mode.
|
||||
gss_spnego: GSS_SPNEGO_Subsequent &parse-from=self.serverSaslCreds[0].payload
|
||||
if (ctx.saslMechanism == "GSS-SPNEGO" && |self.serverSaslCreds| > 0) {
|
||||
|
||||
if ( $$?.negTokenResp ) {
|
||||
local token = $$.negTokenResp;
|
||||
if ( token.accepted && token?.supportedMechOid ) {
|
||||
if ( token.supportedMechOid == GSSAPI_MECH_MS_KRB5 ) {
|
||||
ctx.messageMode = MessageMode::MS_KRB5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -980,16 +1076,61 @@ type AbandonRequest = unit(inout message: Message) {
|
|||
#-----------------------------------------------------------------------------
|
||||
# Extended Operation
|
||||
# https://tools.ietf.org/html/rfc4511#section-4.12
|
||||
type ExtendedRequest = unit(inout message: Message, ctx: Ctx&) {
|
||||
var requestValue: bytes;
|
||||
header: ASN1::ASN1Header &requires=($$.tag.class == ASN1::ASN1Class::ContextSpecific);
|
||||
requestName: bytes &size=self.header.len.len &convert=$$.decode(spicy::Charset::ASCII) {
|
||||
message.obj = $$;
|
||||
}
|
||||
|
||||
# TODO: implement ExtendedRequest
|
||||
# type ExtendedRequest = unit(inout message: Message) {
|
||||
#
|
||||
# };
|
||||
# If there's more byte to parse, it's the requestValue.
|
||||
: ASN1::ASN1Message(False)
|
||||
&requires=($$.head.tag.class == ASN1::ASN1Class::ContextSpecific)
|
||||
if ( message.opLen > self.offset() ) {
|
||||
|
||||
# TODO: implement ExtendedResponse
|
||||
# type ExtendedResponse = unit(inout message: Message) {
|
||||
#
|
||||
# };
|
||||
self.requestValue = $$.application_data;
|
||||
}
|
||||
|
||||
on %done {
|
||||
# Did the client request StartTLS?
|
||||
#
|
||||
# https://datatracker.ietf.org/doc/html/rfc4511#section-4.14.1
|
||||
if ( self.requestName == "1.3.6.1.4.1.1466.20037" )
|
||||
ctx.startTlsRequested = True;
|
||||
}
|
||||
};
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
type ExtendedResponseEntry = unit(inout r: ExtendedResponse) {
|
||||
: ASN1::ASN1Message(False) &requires=($$.head.tag.class == ASN1::ASN1Class::ContextSpecific) {
|
||||
if ( $$.head.tag.type_ == ASN1::ASN1Type(10) )
|
||||
r.responseName = $$.application_data;
|
||||
else if ( $$.head.tag.type_ == ASN1::ASN1Type(11) )
|
||||
r.responseValue = $$.application_data;
|
||||
else
|
||||
throw "Unhandled extended response tag %s" % $$.head.tag;
|
||||
}
|
||||
};
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
type ExtendedResponse = unit(inout message: Message, ctx: Ctx&) {
|
||||
var responseName: bytes;
|
||||
var responseValue: bytes;
|
||||
: Result {
|
||||
message.result_ = $$;
|
||||
}
|
||||
|
||||
# Try to parse two ASN1 entries if there are bytes left in the unit.
|
||||
# Both are optional and identified by context specific tagging.
|
||||
: ExtendedResponseEntry(self) if ( message.opLen > self.offset() );
|
||||
: ExtendedResponseEntry(self) if ( message.opLen > self.offset() );
|
||||
|
||||
on %done {
|
||||
# Client had requested StartTLS and it was successful? Switch to SSL.
|
||||
if ( ctx.startTlsRequested && message.result_.code == ResultCode::SUCCESS )
|
||||
ctx.messageMode = MessageMode::TLS;
|
||||
}
|
||||
};
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# IntermediateResponse Message
|
||||
|
|
12
src/analyzer/protocol/ldap/ldap_zeek.spicy
Normal file
12
src/analyzer/protocol/ldap/ldap_zeek.spicy
Normal file
|
@ -0,0 +1,12 @@
|
|||
module LDAP_Zeek;
|
||||
|
||||
import LDAP;
|
||||
import zeek;
|
||||
|
||||
on LDAP::TlsForward::%init {
|
||||
zeek::protocol_begin("SSL");
|
||||
}
|
||||
|
||||
on LDAP::TlsForward::chunk {
|
||||
zeek::protocol_data_in(zeek::is_orig(), self.chunk);
|
||||
}
|
|
@ -195,7 +195,6 @@ event modbus_write_multiple_registers_response%(c: connection, headers: ModbusHe
|
|||
##
|
||||
## refs: A vector of reference records.
|
||||
event modbus_read_file_record_request%(c: connection, headers: ModbusHeaders, byte_count: count, refs: ModbusFileRecordRequests%);
|
||||
event modbus_read_file_record_request%(c: connection, headers: ModbusHeaders%) &deprecated="Remove in v7.1. Use the version that takes a byte_count and vector of references";
|
||||
|
||||
## Generated for a Modbus read file record response.
|
||||
##
|
||||
|
@ -207,7 +206,6 @@ event modbus_read_file_record_request%(c: connection, headers: ModbusHeaders%) &
|
|||
##
|
||||
## refs: A vector of reference records.
|
||||
event modbus_read_file_record_response%(c: connection, headers: ModbusHeaders, byte_count: count, refs: ModbusFileRecordResponses%);
|
||||
event modbus_read_file_record_response%(c: connection, headers: ModbusHeaders%) &deprecated="Remove in v7.1. Use the version that takes a byte_count and vector of references";
|
||||
|
||||
## Generated for a Modbus write file record request.
|
||||
##
|
||||
|
@ -219,7 +217,6 @@ event modbus_read_file_record_response%(c: connection, headers: ModbusHeaders%)
|
|||
##
|
||||
## refs: A vector of reference records.
|
||||
event modbus_write_file_record_request%(c: connection, headers: ModbusHeaders, byte_count: count, refs: ModbusFileReferences%);
|
||||
event modbus_write_file_record_request%(c: connection, headers: ModbusHeaders%) &deprecated="Remove in v7.1. Use the version that takes a byte_count and vector of references";
|
||||
|
||||
## Generated for a Modbus write file record response.
|
||||
##
|
||||
|
@ -231,7 +228,6 @@ event modbus_write_file_record_request%(c: connection, headers: ModbusHeaders%)
|
|||
##
|
||||
## refs: A vector of reference records.
|
||||
event modbus_write_file_record_response%(c: connection, headers: ModbusHeaders, byte_count: count, refs: ModbusFileReferences%);
|
||||
event modbus_write_file_record_response%(c: connection, headers: ModbusHeaders%) &deprecated="Remove in v7.1. Use the version that takes a byte_count and vector of references";
|
||||
|
||||
## Generated for a Modbus mask write register request.
|
||||
##
|
||||
|
|
|
@ -84,6 +84,57 @@ event mysql_server_version%(c: connection, ver: string%);
|
|||
##
|
||||
## username: The username supplied by the client
|
||||
##
|
||||
## .. zeek:see:: mysql_command_request mysql_error mysql_ok mysql_server_version
|
||||
## .. zeek:see:: mysql_command_request mysql_error mysql_ok mysql_server_version mysql_ssl_request
|
||||
event mysql_handshake%(c: connection, username: string%);
|
||||
|
||||
## Generated for a short client handshake response packet with the CLIENT_SSL
|
||||
## flag set. Usually the client will initiate a TLS handshake afterwards.
|
||||
#
|
||||
## See the MySQL `documentation <http://dev.mysql.com/doc/internals/en/client-server-protocol.html>`__
|
||||
## for more information about the MySQL protocol.
|
||||
##
|
||||
## c: The connection.
|
||||
##
|
||||
## .. zeek:see:: mysql_handshake
|
||||
event mysql_ssl_request%(c: connection%);
|
||||
|
||||
## Generated for information about plugin authentication within handshake packets.
|
||||
##
|
||||
## c: The connection.
|
||||
##
|
||||
## is_orig: True if this is from the client, false if from the server.
|
||||
##
|
||||
## name: Name of the authentication plugin.
|
||||
##
|
||||
## data: The initial auth data. From the server, it is the concatenation of
|
||||
## auth_plugin_data_part_1 and auth_plugin_data_part_2 in the handshake.
|
||||
## For the client it is the auth_response in the handshake response.
|
||||
##
|
||||
## .. zeek:see:: mysql_handshake mysql_auth_switch_request mysql_auth_more_data
|
||||
event mysql_auth_plugin%(c: connection, is_orig: bool, name: string, data: string%);
|
||||
|
||||
## Generated for a server packet with an auth switch request.
|
||||
##
|
||||
## c: The connection.
|
||||
##
|
||||
## name: The plugin name.
|
||||
##
|
||||
## data: Initial authentication data for the plugin.
|
||||
##
|
||||
## .. zeek:see:: mysql_handshake mysql_auth_more_data
|
||||
event mysql_auth_switch_request%(c: connection, name: string, data: string%);
|
||||
|
||||
## Generated for opaque authentication data exchanged between client and server
|
||||
## after the client's handshake packet, but before the server replied with
|
||||
## an OK_Packet
|
||||
##
|
||||
## Data is specific to the plugin auth mechanism used by client and server.
|
||||
##
|
||||
## c: The connection.
|
||||
##
|
||||
## is_orig: True if this is from the client, false if from the server.
|
||||
##
|
||||
## data: More authentication data.
|
||||
##
|
||||
## .. zeek:see:: mysql_handshake mysql_auth_switch_request
|
||||
event mysql_auth_more_data%(c: connection, is_orig: bool, data: string%);
|
||||
|
|
|
@ -14,6 +14,28 @@ refine flow MySQL_Flow += {
|
|||
connection()->zeek_analyzer()->Conn(),
|
||||
zeek::make_intrusive<zeek::StringVal>(c_str(${msg.handshake9.server_version})));
|
||||
}
|
||||
|
||||
if ( mysql_auth_plugin )
|
||||
{
|
||||
if ( ${msg.version} == 10 && (${msg.handshake10.capability_flags_2} << 16) & CLIENT_PLUGIN_AUTH )
|
||||
{
|
||||
auto auth_plugin = zeek::make_intrusive<zeek::StringVal>(c_str(${msg.handshake10.auth_plugin}));
|
||||
auto data_part_1 = ${msg.handshake10.auth_plugin_data_part_1};
|
||||
auto data_part_2 = ${msg.handshake10.auth_plugin_data_part_2};
|
||||
std::vector<zeek::data_chunk_t> data_parts = {
|
||||
zeek::data_chunk_t{data_part_1.length(), reinterpret_cast<const char*>(data_part_1.begin())},
|
||||
zeek::data_chunk_t{data_part_2.length(), reinterpret_cast<const char*>(data_part_2.begin())},
|
||||
};
|
||||
auto data = zeek::make_intrusive<zeek::StringVal>(zeek::concatenate(data_parts));
|
||||
|
||||
zeek::BifEvent::enqueue_mysql_auth_plugin(connection()->zeek_analyzer(),
|
||||
connection()->zeek_analyzer()->Conn(),
|
||||
false /*is_orig*/,
|
||||
std::move(auth_plugin),
|
||||
std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
%}
|
||||
|
||||
|
@ -23,23 +45,42 @@ refine flow MySQL_Flow += {
|
|||
connection()->zeek_analyzer()->AnalyzerConfirmation();
|
||||
|
||||
// If the client requested SSL and didn't provide credentials, switch to SSL
|
||||
if ( ${msg.version} == 10 && ( ${msg.v10_response.cap_flags} & CLIENT_SSL ) && ${msg.v10_response.credentials}->empty() )
|
||||
if ( ${msg.version} == 10 && ( ${msg.v10_response.cap_flags} & CLIENT_SSL ))
|
||||
{
|
||||
connection()->zeek_analyzer()->StartTLS();
|
||||
|
||||
if ( mysql_ssl_request )
|
||||
zeek::BifEvent::enqueue_mysql_ssl_request(connection()->zeek_analyzer(),
|
||||
connection()->zeek_analyzer()->Conn());
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( mysql_handshake )
|
||||
{
|
||||
if ( ${msg.version} == 10 && ${msg.v10_response.credentials}->size() > 0 )
|
||||
if ( ${msg.version} == 10 )
|
||||
zeek::BifEvent::enqueue_mysql_handshake(connection()->zeek_analyzer(),
|
||||
connection()->zeek_analyzer()->Conn(),
|
||||
zeek::make_intrusive<zeek::StringVal>(c_str(${msg.v10_response.credentials[0].username})));
|
||||
zeek::make_intrusive<zeek::StringVal>(c_str(${msg.v10_response.plain.credentials.username})));
|
||||
if ( ${msg.version} == 9 )
|
||||
zeek::BifEvent::enqueue_mysql_handshake(connection()->zeek_analyzer(),
|
||||
connection()->zeek_analyzer()->Conn(),
|
||||
zeek::make_intrusive<zeek::StringVal>(c_str(${msg.v9_response.username})));
|
||||
}
|
||||
|
||||
if ( mysql_auth_plugin )
|
||||
{
|
||||
if ( ${msg.version} == 10 && ${msg.v10_response.plain.cap_flags} & CLIENT_PLUGIN_AUTH )
|
||||
{
|
||||
auto auth_plugin = zeek::make_intrusive<zeek::StringVal>(c_str(${msg.v10_response.plain.auth_plugin}));
|
||||
auto data = to_stringval(${msg.v10_response.plain.credentials.password.val});
|
||||
zeek::BifEvent::enqueue_mysql_auth_plugin(connection()->zeek_analyzer(),
|
||||
connection()->zeek_analyzer()->Conn(),
|
||||
true /*is_orig*/,
|
||||
std::move(auth_plugin),
|
||||
std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
%}
|
||||
|
||||
|
@ -83,8 +124,8 @@ refine flow MySQL_Flow += {
|
|||
|
||||
function proc_resultset(msg: Resultset): bool
|
||||
%{
|
||||
if ( ${msg.is_eof} )
|
||||
return true; // Raised through proc_eof_packet()
|
||||
if ( ${msg.is_eof_or_ok} )
|
||||
return true; // Raised through proc_eof_packet() or proc_ok_packet()
|
||||
|
||||
if ( ! mysql_result_row )
|
||||
return true;
|
||||
|
@ -112,6 +153,24 @@ refine flow MySQL_Flow += {
|
|||
return true;
|
||||
%}
|
||||
|
||||
function proc_auth_switch_request(msg: AuthSwitchRequest): bool
|
||||
%{
|
||||
zeek::BifEvent::enqueue_mysql_auth_switch_request(connection()->zeek_analyzer(),
|
||||
connection()->zeek_analyzer()->Conn(),
|
||||
zeek::make_intrusive<zeek::StringVal>(c_str(${msg.name})),
|
||||
to_stringval(${msg.data}));
|
||||
return true;
|
||||
%}
|
||||
|
||||
function proc_auth_more_data(msg: AuthMoreData): bool
|
||||
%{
|
||||
zeek::BifEvent::enqueue_mysql_auth_more_data(connection()->zeek_analyzer(),
|
||||
connection()->zeek_analyzer()->Conn(),
|
||||
${is_orig},
|
||||
to_stringval(${msg.data}));
|
||||
return true;
|
||||
%}
|
||||
|
||||
};
|
||||
|
||||
refine typeattr Initial_Handshake_Packet += &let {
|
||||
|
@ -141,3 +200,11 @@ refine typeattr EOF_Packet += &let {
|
|||
refine typeattr Resultset += &let {
|
||||
proc = $context.flow.proc_resultset(this);
|
||||
};
|
||||
|
||||
refine typeattr AuthSwitchRequest += &let {
|
||||
proc = $context.flow.proc_auth_switch_request(this);
|
||||
};
|
||||
|
||||
refine typeattr AuthMoreData += &let {
|
||||
proc = $context.flow.proc_auth_more_data(this);
|
||||
};
|
||||
|
|
|
@ -140,6 +140,11 @@ enum state {
|
|||
COMMAND_PHASE = 1,
|
||||
};
|
||||
|
||||
enum ConnectionExpected {
|
||||
EXPECT_HANDSHAKE,
|
||||
EXPECT_AUTH_DATA,
|
||||
};
|
||||
|
||||
enum Expected {
|
||||
NO_EXPECTATION,
|
||||
EXPECT_STATUS,
|
||||
|
@ -158,12 +163,133 @@ enum EOFType {
|
|||
};
|
||||
|
||||
enum Client_Capabilities {
|
||||
CLIENT_CONNECT_WITH_DB = 0x00000008,
|
||||
CLIENT_SSL = 0x00000800,
|
||||
CLIENT_PLUGIN_AUTH = 0x00080000,
|
||||
CLIENT_CONNECT_ATTRS = 0x00100000,
|
||||
# Expects an OK (instead of EOF) after the resultset rows of a Text Resultset.
|
||||
CLIENT_DEPRECATE_EOF = 0x01000000,
|
||||
CLIENT_ZSTD_COMPRESSION_ALGORITHM = 0x04000000,
|
||||
CLIENT_QUERY_ATTRIBUTES = 0x08000000,
|
||||
};
|
||||
|
||||
# Binary Protocol Resultset encoding.
|
||||
#
|
||||
# https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html
|
||||
#
|
||||
# Values taken from here: https://dev.mysql.com/doc/dev/mysql-server/latest/namespaceclassic__protocol_1_1field__type.html
|
||||
enum field_types {
|
||||
TYPE_DECIMAL = 0x00,
|
||||
TYPE_TINY = 0x01,
|
||||
TYPE_SHORT = 0x02,
|
||||
TYPE_LONG = 0x03,
|
||||
TYPE_FLOAT = 0x04,
|
||||
TYPE_DOUBLE = 0x05,
|
||||
TYPE_NULL = 0x06,
|
||||
TYPE_TIMESTAMP = 0x07,
|
||||
TYPE_LONGLONG = 0x08,
|
||||
TYPE_INT24 = 0x09,
|
||||
TYPE_DATE = 0x0a,
|
||||
TYPE_TIME = 0x0b,
|
||||
TYPE_DATETIME = 0x0c,
|
||||
TYPE_YEAR = 0x0d,
|
||||
TYPE_VARCHAR = 0x0f,
|
||||
TYPE_BIT = 0x10,
|
||||
TYPE_TIMESTAMP2 = 0x11,
|
||||
TYPE_JSON = 0xf5,
|
||||
TYPE_NEWDECIMAL = 0xf6,
|
||||
TYPE_ENUM = 0xf7,
|
||||
TYPE_SET = 0xf8,
|
||||
TYPE_TINYBLOB = 0xf9,
|
||||
TYPE_MEDIUMBLOB = 0xfa,
|
||||
TYPE_LONGBLOB = 0xfb,
|
||||
TYPE_BLOB = 0xfc,
|
||||
TYPE_VARSTRING = 0xfd,
|
||||
TYPE_STRING = 0xfe,
|
||||
TYPE_GEOMETRY = 0xff,
|
||||
};
|
||||
|
||||
type Date = record {
|
||||
year : int16;
|
||||
month: int8;
|
||||
day : int8;
|
||||
};
|
||||
|
||||
type Time = record {
|
||||
hour : int8;
|
||||
minute: int8;
|
||||
second: int8;
|
||||
};
|
||||
|
||||
type BinaryDate = record {
|
||||
len: uint8 &enforce(len == 0 || len == 4 || len == 7 || len == 11);
|
||||
have_date: case ( len > 0 ) of {
|
||||
true -> date : Date;
|
||||
false -> none_1: empty;
|
||||
};
|
||||
have_time: case ( len > 4 ) of {
|
||||
true -> time : Time;
|
||||
false -> none_2: empty;
|
||||
};
|
||||
have_micros: case ( len > 7 ) of {
|
||||
true -> micros: int32;
|
||||
false -> none_3: empty;
|
||||
};
|
||||
};
|
||||
|
||||
type DurationTime = record {
|
||||
is_negative: int8 &enforce(is_negative == 0 || is_negative == 1);
|
||||
days : int32;
|
||||
time : Time;
|
||||
};
|
||||
|
||||
type BinaryTime = record {
|
||||
len: uint8 &enforce(len == 0 || len == 8 || len == 12);
|
||||
have_time: case ( len > 0 ) of {
|
||||
true -> time : DurationTime;
|
||||
false -> none_1: empty;
|
||||
};
|
||||
have_micros: case ( len > 8 ) of {
|
||||
true -> micros: int32;
|
||||
false -> none_2: empty;
|
||||
};
|
||||
};
|
||||
|
||||
type BinaryValue(type: uint16) = record {
|
||||
value: case ( type ) of {
|
||||
TYPE_DECIMAL -> decimal_val: LengthEncodedInteger;
|
||||
TYPE_TINY -> tiny_val: int8;
|
||||
TYPE_SHORT -> short_val: int16;
|
||||
TYPE_LONG -> long_val: int32;
|
||||
TYPE_FLOAT -> float_val: bytestring &length=4;
|
||||
TYPE_DOUBLE -> double_val: bytestring &length=8;
|
||||
TYPE_NULL -> null_val: empty; # in null_bitmap
|
||||
TYPE_TIMESTAMP -> timestamp_val: BinaryDate;
|
||||
TYPE_LONGLONG -> longlong_val: int64;
|
||||
TYPE_INT24 -> int24_val: int32;
|
||||
TYPE_DATE -> date_val: BinaryDate;
|
||||
TYPE_TIME -> time_val: BinaryTime;
|
||||
TYPE_DATETIME -> datetime_val: BinaryDate;
|
||||
TYPE_YEAR -> year_val: int16;
|
||||
TYPE_VARCHAR -> varchar_val: LengthEncodedString;
|
||||
TYPE_BIT -> bit_val: LengthEncodedString;
|
||||
TYPE_TIMESTAMP2 -> timestamp2_val: BinaryDate;
|
||||
TYPE_JSON -> json_val: LengthEncodedString;
|
||||
TYPE_NEWDECIMAL -> newdecimal_val: LengthEncodedString;
|
||||
TYPE_ENUM -> enum_val: LengthEncodedString;
|
||||
TYPE_SET -> set_val: LengthEncodedString;
|
||||
TYPE_TINYBLOB -> tinyblob_val: LengthEncodedString;
|
||||
TYPE_MEDIUMBLOB -> mediumblob_val: LengthEncodedString;
|
||||
TYPE_LONGBLOB -> longblob_val: LengthEncodedString;
|
||||
TYPE_BLOB -> blob_val: LengthEncodedString;
|
||||
TYPE_VARSTRING -> varstring_val: LengthEncodedString;
|
||||
TYPE_STRING -> string_val: LengthEncodedString;
|
||||
TYPE_GEOMETRY -> geometry_val: LengthEncodedString;
|
||||
};
|
||||
};
|
||||
|
||||
type NUL_String = RE/[^\0]*\0/;
|
||||
type EmptyOrNUL_String = RE/([^\0]*\0)?/;
|
||||
|
||||
# MySQL PDU
|
||||
|
||||
|
@ -193,7 +319,7 @@ type Server_Message(seq_id: uint8, pkt_len: uint32) = case is_initial of {
|
|||
};
|
||||
|
||||
type Client_Message(state: int) = case state of {
|
||||
CONNECTION_PHASE -> connection_phase: Handshake_Response_Packet;
|
||||
CONNECTION_PHASE -> connection_phase: Connection_Phase_Packets;
|
||||
COMMAND_PHASE -> command_phase : Command_Request_Packet;
|
||||
};
|
||||
|
||||
|
@ -219,8 +345,24 @@ type Handshake_v10 = record {
|
|||
character_set : uint8;
|
||||
status_flags : uint16;
|
||||
capability_flags_2 : uint16;
|
||||
auth_plugin_data_len : uint8;
|
||||
auth_plugin_name : NUL_String;
|
||||
auth_plugin_data_len : uint8 &enforce( auth_plugin_data_len==0 || auth_plugin_data_len >= 21);
|
||||
reserved : padding[10];
|
||||
auth_plugin_data_part_2: bytestring &length=auth_plugin_data_part_2_len;
|
||||
have_plugin : case ( ( capability_flags_2 << 16 ) & CLIENT_PLUGIN_AUTH ) of {
|
||||
CLIENT_PLUGIN_AUTH -> auth_plugin: NUL_String;
|
||||
0x0 -> none : empty;
|
||||
};
|
||||
} &let {
|
||||
# The length of auth_plugin_data_part_2 is at least 13 bytes,
|
||||
# or auth_plugin_data_len - 8 if that is larger, check for
|
||||
# auth_plugin_data_len > 21 (8 + 13) to prevent underflow for
|
||||
# when subtracting 8.
|
||||
#
|
||||
# https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html
|
||||
auth_plugin_data_part_2_len = auth_plugin_data_len > 21 ? auth_plugin_data_len - 8 : 13;
|
||||
update_auth_plugin: bool = $context.connection.set_auth_plugin(auth_plugin)
|
||||
&if( ( capability_flags_2 << 16 ) & CLIENT_PLUGIN_AUTH );
|
||||
server_query_attrs: bool = $context.connection.set_server_query_attrs(( capability_flags_2 << 16 ) & CLIENT_QUERY_ATTRIBUTES);
|
||||
};
|
||||
|
||||
type Handshake_v9 = record {
|
||||
|
@ -240,7 +382,45 @@ type Handshake_Response_Packet = case $context.connection.get_version() of {
|
|||
|
||||
type Handshake_Credentials_v10 = record {
|
||||
username : NUL_String;
|
||||
password : bytestring &restofdata;
|
||||
password : LengthEncodedString;
|
||||
};
|
||||
|
||||
type Connection_Attribute = record {
|
||||
name : LengthEncodedString;
|
||||
value : LengthEncodedString;
|
||||
};
|
||||
|
||||
type Handshake_Connection_Attributes = record {
|
||||
length : uint8;
|
||||
attrs : Connection_Attribute[] &until($input.length() == 0);
|
||||
} &length = length+1;
|
||||
|
||||
type Handshake_Plain_v10(cap_flags: uint32) = record {
|
||||
credentials: Handshake_Credentials_v10;
|
||||
have_db : case ( cap_flags & CLIENT_CONNECT_WITH_DB ) of {
|
||||
CLIENT_CONNECT_WITH_DB -> database: NUL_String;
|
||||
0x0 -> none_1 : empty;
|
||||
};
|
||||
have_plugin : case ( cap_flags & CLIENT_PLUGIN_AUTH ) of {
|
||||
CLIENT_PLUGIN_AUTH -> auth_plugin: EmptyOrNUL_String;
|
||||
0x0 -> none_2 : empty;
|
||||
};
|
||||
have_attrs : case ( cap_flags & CLIENT_CONNECT_ATTRS ) of {
|
||||
CLIENT_CONNECT_ATTRS -> conn_attrs: Handshake_Connection_Attributes;
|
||||
0x0 -> none_3 : empty;
|
||||
};
|
||||
have_zstd : case ( cap_flags & CLIENT_ZSTD_COMPRESSION_ALGORITHM ) of {
|
||||
CLIENT_ZSTD_COMPRESSION_ALGORITHM -> zstd_compression_level: uint8;
|
||||
0x0 -> none_4 : empty;
|
||||
};
|
||||
} &let {
|
||||
update_auth_plugin: bool = $context.connection.set_auth_plugin(auth_plugin)
|
||||
&if( cap_flags & CLIENT_PLUGIN_AUTH );
|
||||
|
||||
# Switch client state into expecting more auth data. If the server responds
|
||||
# with an OK_Packet before, will switch into COMMAND_PHASE.
|
||||
update_conn_expectation: bool = $context.connection.set_next_conn_expected(EXPECT_AUTH_DATA)
|
||||
&if( cap_flags & CLIENT_PLUGIN_AUTH );
|
||||
};
|
||||
|
||||
type Handshake_Response_Packet_v10 = record {
|
||||
|
@ -248,9 +428,13 @@ type Handshake_Response_Packet_v10 = record {
|
|||
max_pkt_size: uint32;
|
||||
char_set : uint8;
|
||||
pad : padding[23];
|
||||
credentials : Handshake_Credentials_v10[] &until($input.length() == 0);
|
||||
use_ssl : case ( cap_flags & CLIENT_SSL ) of {
|
||||
CLIENT_SSL -> none : empty;
|
||||
default -> plain: Handshake_Plain_v10(cap_flags);
|
||||
};
|
||||
} &let {
|
||||
deprecate_eof: bool = $context.connection.set_deprecate_eof(cap_flags & CLIENT_DEPRECATE_EOF);
|
||||
client_query_attrs: bool = $context.connection.set_client_query_attrs(cap_flags & CLIENT_QUERY_ATTRIBUTES);
|
||||
};
|
||||
|
||||
type Handshake_Response_Packet_v9 = record {
|
||||
|
@ -258,17 +442,71 @@ type Handshake_Response_Packet_v9 = record {
|
|||
max_pkt_size : uint24le;
|
||||
username : NUL_String;
|
||||
auth_response: NUL_String;
|
||||
have_db : case ( cap_flags & 0x8 ) of {
|
||||
0x8 -> database: NUL_String;
|
||||
have_db : case ( cap_flags & CLIENT_CONNECT_WITH_DB ) of {
|
||||
CLIENT_CONNECT_WITH_DB -> database: NUL_String;
|
||||
0x0 -> none : empty;
|
||||
};
|
||||
password : bytestring &restofdata;
|
||||
};
|
||||
|
||||
# Connection Phase
|
||||
|
||||
type Connection_Phase_Packets = case $context.connection.get_conn_expectation() of {
|
||||
EXPECT_HANDSHAKE -> handshake_resp: Handshake_Response_Packet;
|
||||
EXPECT_AUTH_DATA -> auth_data: AuthMoreData(true);
|
||||
};
|
||||
|
||||
# Query attribute handling for COM_QUERY
|
||||
#
|
||||
type AttributeTypeAndName = record {
|
||||
type: uint8;
|
||||
unsigned_flag: uint8;
|
||||
name: LengthEncodedString;
|
||||
};
|
||||
|
||||
type AttributeValue(is_null: bool, type: uint8) = record {
|
||||
null: case is_null of {
|
||||
false -> val: BinaryValue(type);
|
||||
true -> null_val: empty;
|
||||
};
|
||||
} &let {
|
||||
# Move parsing the next query attribute.
|
||||
done = $context.connection.next_query_attr();
|
||||
};
|
||||
|
||||
type Attributes(count: int) = record {
|
||||
null_bitmap : bytestring &length=(count + 7) / 8;
|
||||
send_types_to_server: uint8 &enforce(send_types_to_server == 1);
|
||||
names : AttributeTypeAndName[count];
|
||||
values : AttributeValue(
|
||||
# Check if null_bitmap contains this attribute index. This
|
||||
# will pass true if the attribute value is NULL and parsing
|
||||
# skipped in AttributeValue above.
|
||||
(null_bitmap[$context.connection.query_attr_idx() / 8] >> ($context.connection.query_attr_idx() % 8)) & 0x01,
|
||||
names[$context.connection.query_attr_idx()].type
|
||||
)[] &until($context.connection.query_attr_idx() >= count);
|
||||
};
|
||||
|
||||
type Query_Attributes = record {
|
||||
count : LengthEncodedInteger;
|
||||
set_count: LengthEncodedInteger;
|
||||
have_attr: case ( attr_count > 0 ) of {
|
||||
true -> attrs: Attributes(attr_count);
|
||||
false -> none: empty;
|
||||
} &requires(new_query_attrs);
|
||||
} &let {
|
||||
attr_count: int = to_int()(count);
|
||||
new_query_attrs = $context.connection.new_query_attrs();
|
||||
};
|
||||
|
||||
# Command Request
|
||||
|
||||
type Command_Request_Packet = record {
|
||||
command: uint8;
|
||||
attrs : case ( command == COM_QUERY && $context.connection.get_client_query_attrs() && $context.connection.get_server_query_attrs() ) of {
|
||||
true -> query_attrs: Query_Attributes;
|
||||
false -> none: empty;
|
||||
};
|
||||
arg : bytestring &restofdata;
|
||||
} &let {
|
||||
update_expectation: bool = $context.connection.set_next_expected_from_command(command);
|
||||
|
@ -292,6 +530,10 @@ type Command_Response_Status = record {
|
|||
pkt_type: uint8;
|
||||
response: case pkt_type of {
|
||||
0x00 -> data_ok: OK_Packet;
|
||||
# When still in the CONNECTION_PHASE, the server can reply
|
||||
# with AuthMoreData which is 0x01 stuffed opaque payload.
|
||||
# https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_more_data.html
|
||||
0x01 -> auth_more_data: AuthMoreData(false);
|
||||
0xfe -> data_eof: EOF_Packet(EOF_END);
|
||||
0xff -> data_err: ERR_Packet;
|
||||
default -> unknown: empty;
|
||||
|
@ -326,22 +568,22 @@ type ColumnDefinition = record {
|
|||
};
|
||||
|
||||
# Only used to indicate the end of a result, no intermediate eofs here.
|
||||
type EOFOrOK = case $context.connection.get_deprecate_eof() of {
|
||||
# MySQL spec says "You must check whether the packet length is less than 9
|
||||
# to make sure that it is a EOF_Packet packet" so the value of 13 here
|
||||
# comes from that 9, plus a 4-byte header.
|
||||
type EOFOrOK(pkt_len: uint32) = case ( $context.connection.get_deprecate_eof() || pkt_len > 13 ) of {
|
||||
false -> eof: EOF_Packet(EOF_END);
|
||||
true -> ok: OK_Packet;
|
||||
};
|
||||
|
||||
type ColumnDefinitionOrEOF(pkt_len: uint32) = record {
|
||||
marker : uint8;
|
||||
def_or_eof: case is_eof of {
|
||||
true -> eof: EOFOrOK;
|
||||
def_or_eof: case is_eof_or_ok of {
|
||||
true -> eof: EOFOrOK(pkt_len);
|
||||
false -> def: ColumnDefinition41(marker);
|
||||
} &requires(is_eof);
|
||||
} &requires(is_eof_or_ok);
|
||||
} &let {
|
||||
# MySQL spec says "You must check whether the packet length is less than 9
|
||||
# to make sure that it is a EOF_Packet packet" so the value of 13 here
|
||||
# comes from that 9, plus a 4-byte header.
|
||||
is_eof: bool = (marker == 0xfe && pkt_len < 13);
|
||||
is_eof_or_ok: bool = (marker == 0xfe);
|
||||
};
|
||||
|
||||
|
||||
|
@ -350,22 +592,19 @@ type EOFIfLegacyThenResultset(pkt_len: uint32) = case $context.connection.get_de
|
|||
true -> resultset: Resultset(pkt_len);
|
||||
} &let {
|
||||
update_result_seen: bool = $context.connection.set_results_seen(0);
|
||||
update_expectation: bool = $context.connection.set_next_expected(EXPECT_RESULTSET);
|
||||
update_expectation: bool = $context.connection.set_next_expected(EXPECT_RESULTSET) &if( ! $context.connection.get_deprecate_eof() );
|
||||
};
|
||||
|
||||
type Resultset(pkt_len: uint32) = record {
|
||||
marker : uint8;
|
||||
row_or_eof: case is_eof of {
|
||||
true -> eof: EOFOrOK;
|
||||
row_or_eof: case is_eof_or_ok of {
|
||||
true -> eof: EOFOrOK(pkt_len);
|
||||
false -> row: ResultsetRow(marker);
|
||||
} &requires(is_eof);
|
||||
} &requires(is_eof_or_ok);
|
||||
} &let {
|
||||
# MySQL spec says "You must check whether the packet length is less than 9
|
||||
# to make sure that it is a EOF_Packet packet" so the value of 13 here
|
||||
# comes from that 9, plus a 4-byte header.
|
||||
is_eof : bool = (marker == 0xfe && pkt_len < 13);
|
||||
is_eof_or_ok : bool = (marker == 0xfe);
|
||||
update_result_seen: bool = $context.connection.inc_results_seen();
|
||||
update_expectation: bool = $context.connection.set_next_expected(is_eof ? NO_EXPECTATION : EXPECT_RESULTSET);
|
||||
update_expectation: bool = $context.connection.set_next_expected(is_eof_or_ok ? NO_EXPECTATION : EXPECT_RESULTSET);
|
||||
};
|
||||
|
||||
type ResultsetRow(first_byte: uint8) = record {
|
||||
|
@ -389,10 +628,20 @@ type ColumnDefinition41(first_byte: uint8) = record {
|
|||
filler : padding[2];
|
||||
};
|
||||
|
||||
# Opaque auth data exchanged during the connection phase between client and server.
|
||||
type AuthMoreData(is_orig: bool) = record {
|
||||
data : bytestring &restofdata;
|
||||
};
|
||||
|
||||
type AuthSwitchRequest = record {
|
||||
status: uint8;
|
||||
status: uint8 &enforce(status==254);
|
||||
name : NUL_String;
|
||||
data : bytestring &restofdata;
|
||||
} &let {
|
||||
update_auth_plugin : bool = $context.connection.set_auth_plugin(name);
|
||||
update_conn_expectation: bool = $context.connection.set_next_conn_expected(EXPECT_AUTH_DATA);
|
||||
# After an AuthSwitchRequest, server replies with OK_Packet, ERR_Packet or AuthMoreData.
|
||||
update_expectation: bool = $context.connection.set_next_expected(EXPECT_STATUS);
|
||||
};
|
||||
|
||||
type ColumnDefinition320 = record {
|
||||
|
@ -440,10 +689,15 @@ refine connection MySQL_Conn += {
|
|||
uint8 previous_seq_id_;
|
||||
int state_;
|
||||
Expected expected_;
|
||||
ConnectionExpected conn_expected_;
|
||||
uint32 col_count_;
|
||||
uint32 remaining_cols_;
|
||||
uint32 results_seen_;
|
||||
bool deprecate_eof_;
|
||||
bool server_query_attrs_;
|
||||
bool client_query_attrs_;
|
||||
std::string auth_plugin_;
|
||||
int query_attr_idx_;
|
||||
%}
|
||||
|
||||
%init{
|
||||
|
@ -451,10 +705,14 @@ refine connection MySQL_Conn += {
|
|||
previous_seq_id_ = 0;
|
||||
state_ = CONNECTION_PHASE;
|
||||
expected_ = EXPECT_STATUS;
|
||||
conn_expected_ = EXPECT_HANDSHAKE;
|
||||
col_count_ = 0;
|
||||
remaining_cols_ = 0;
|
||||
results_seen_ = 0;
|
||||
deprecate_eof_ = false;
|
||||
server_query_attrs_ = false;
|
||||
client_query_attrs_ = false;
|
||||
query_attr_idx_ = 0;
|
||||
%}
|
||||
|
||||
function get_version(): uint8
|
||||
|
@ -487,6 +745,10 @@ refine connection MySQL_Conn += {
|
|||
function update_state(s: state): bool
|
||||
%{
|
||||
state_ = s;
|
||||
|
||||
if ( s == COMMAND_PHASE )
|
||||
conn_expected_ = EXPECT_HANDSHAKE; // Reset connection phase expectation
|
||||
|
||||
return true;
|
||||
%}
|
||||
|
||||
|
@ -501,6 +763,41 @@ refine connection MySQL_Conn += {
|
|||
return true;
|
||||
%}
|
||||
|
||||
function get_server_query_attrs(): bool
|
||||
%{
|
||||
return server_query_attrs_;
|
||||
%}
|
||||
|
||||
function set_server_query_attrs(q: bool): bool
|
||||
%{
|
||||
server_query_attrs_ = q;
|
||||
return true;
|
||||
%}
|
||||
|
||||
function get_client_query_attrs(): bool
|
||||
%{
|
||||
return client_query_attrs_;
|
||||
%}
|
||||
|
||||
function set_client_query_attrs(q: bool): bool
|
||||
%{
|
||||
client_query_attrs_ = q;
|
||||
return true;
|
||||
%}
|
||||
|
||||
function set_auth_plugin(a: bytestring): bool
|
||||
%{
|
||||
// binpac::std_str() includes trailing \0 from parsing.
|
||||
auto new_auth_plugin = std::string(binpac::c_str(a));
|
||||
if ( ! auth_plugin_.empty() && new_auth_plugin != auth_plugin_ )
|
||||
{
|
||||
expected_ = EXPECT_AUTH_SWITCH;
|
||||
}
|
||||
|
||||
auth_plugin_ = std::move(new_auth_plugin);
|
||||
return true;
|
||||
%}
|
||||
|
||||
function get_expectation(): Expected
|
||||
%{
|
||||
return expected_;
|
||||
|
@ -512,6 +809,17 @@ refine connection MySQL_Conn += {
|
|||
return true;
|
||||
%}
|
||||
|
||||
function get_conn_expectation(): ConnectionExpected
|
||||
%{
|
||||
return conn_expected_;
|
||||
%}
|
||||
|
||||
function set_next_conn_expected(c: ConnectionExpected): bool
|
||||
%{
|
||||
conn_expected_ = c;
|
||||
return true;
|
||||
%}
|
||||
|
||||
function set_next_expected_from_command(cmd: uint8): bool
|
||||
%{
|
||||
switch ( cmd ) {
|
||||
|
@ -662,4 +970,21 @@ refine connection MySQL_Conn += {
|
|||
++results_seen_;
|
||||
return true;
|
||||
%}
|
||||
|
||||
function query_attr_idx(): int
|
||||
%{
|
||||
return query_attr_idx_;
|
||||
%}
|
||||
|
||||
function new_query_attrs(): bool
|
||||
%{
|
||||
query_attr_idx_ = 0;
|
||||
return true;
|
||||
%}
|
||||
|
||||
function next_query_attr(): bool
|
||||
%{
|
||||
query_attr_idx_++;
|
||||
return true;
|
||||
%}
|
||||
};
|
||||
|
|
|
@ -413,7 +413,7 @@ type SMB2_error_response(header: SMB2_Header) = record {
|
|||
byte_count : uint32;
|
||||
# This is implemented incorrectly and is disabled for now.
|
||||
#error_data : SMB2_error_data(header, byte_count);
|
||||
stuff : bytestring &restofdata &transient;
|
||||
stuff : bytestring &length=byte_count &transient;
|
||||
} &byteorder = littleendian;
|
||||
|
||||
type SMB2_logoff_request(header: SMB2_Header) = record {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue