diff --git a/.cmake-format.json b/.cmake-format.json index 3061265a04..cb071ca269 100644 --- a/.cmake-format.json +++ b/.cmake-format.json @@ -69,7 +69,8 @@ "kwargs": { "NAME": "*", "PACKAGE_NAME": "*", - "SOURCES": "*" + "SOURCES": "*", + "MODULES": "*" } } } diff --git a/.typos.toml b/.typos.toml index cc29d0abb1..06c59b1068 100644 --- a/.typos.toml +++ b/.typos.toml @@ -69,3 +69,5 @@ caf = "caf" helo = "helo" # Seems we use this in the management framework requestor = "requestor" +# `inout` is used as a keyword in Spicy, but looks like a typo of `input`. +inout = "inout" diff --git a/CHANGES b/CHANGES index 25b35f7b10..8ff2f37e3e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,89 @@ +6.1.0-dev.528 | 2023-10-10 20:03:31 +0200 + + * Introduce dedicated `LDAP::Info` (Benjamin Bannier, Corelight) + + * Remove redundant storing of protocol in LDAP logs (Benjamin Bannier, Corelight) + + * Use LDAP `RemovalHook` instead of implementing `connection_state_remove` (Benjamin Bannier, Corelight) + + * Tidy up LDAP code by using local references (Benjamin Bannier, Corelight) + + * Pluralize container names in LDAP types (Benjamin Bannier, Corelight) + + * Move LDAP script constants to their own file (Benjamin Bannier, Corelight) + + * Name `LDAP::Message` and `LDAP::Search` `*Info` (Benjamin Bannier, Corelight) + + * Make ports for LDAP analyzers fully configurable (Benjamin Bannier, Corelight) + + This moves the ports the LDAP analyzers should be triggered on from the + EVT file to the Zeek module. This gives users full control over which + ports the analyzers are registered for while previously they could only + register them for additional ports (there is no Zeek script equivalent + of `Manager::UnregisterAnalyzerForPort`). + + The analyzers could still be triggered via DPD, but this is intentional. + To fully disable analyzers users can use e.g., + + ```zeek + event zeek_init() + { + Analyzer::disable_analyzer(Analyzer::ANALYZER_LDAP_TCP); + } + ``` + + * Require have-spicy for tests which log spicy-ldap information (Benjamin Bannier, Corelight) + + * Fix LDAP analyzer setup for when Spicy analyzers are disabled (Benjamin Bannier, Corelight) + + * Bump zeek-testing-private (Benjamin Bannier, Corelight) + + * Integrate spicy-ldap test suite (Benjamin Bannier, Corelight) + + * Move spicy-ldap into Zeek protocol analyzer tree (Benjamin Bannier, Corelight) + + * Explicitly use all of spicy-ldap's modules (Benjamin Bannier, Corelight) + + This fixes building of this multi-module analyzer. + + * Explicitly list `asn1.spicy` as spicy-ldap source (Benjamin Bannier, Corelight) + + This will lead to the file being installed so it can be consumed by + other parsers. + + * Remove uses of `zeek` module in spicy-ldap (Benjamin Bannier, Corelight) + + * Fix typos in spicy-ldap (Benjamin Bannier, Corelight) + + * Remove project configuration files in spicy-ldap (Benjamin Bannier, Corelight) + + * Integrate spicy-ldap into build (Benjamin Bannier, Corelight) + + * Import zeek/spicy-ldap@57b5eff9883d1c43896f4278218351a132de9ce1 (Benjamin Bannier, Corelight) + + * Merge branch 'topic/timw/arm-build-failure' (Tim Wojtulewicz, Corelight) + + * topic/timw/arm-build-failure: + CI: Disable spicy on arm_debian11 and opensuse_leap_15_4 builds + CI: Remove sanitizers resource template + + * CI: Disable spicy on arm_debian11 and opensuse_leap_15_4 builds (Tim Wojtulewicz, Corelight) + + These two builds are struggling to complete due to OOM issues. Reducing the + number of CPUs didn't help, so this temporarily disables Spicy from + building on those hosts to reduce the memory usage. + + * CI: Remove sanitizers resource template (Tim Wojtulewicz, Corelight) + + This template was added when the sanitizer builds needed more memory than all + of the other builds. We've since increased the amount of memory that the other + builds use beyond what's requested in this template, so it doesn't make sense + to keep it around. + + * Updating zeek-aux submodule [nomail] (Arne Welzel, Corelight) + + * NEWS: Polish external plugins message [nomail] [skip ci] (Arne Welzel, Corelight) + 6.1.0-dev.502 | 2023-10-09 16:26:58 +0200 * btest/plugins/hooks: Run in bare mode (Arne Welzel, Corelight) diff --git a/NEWS b/NEWS index 8262d1618e..deddf4f8c7 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,25 @@ Breaking Changes New Functionality ----------------- +- Zeek now includes the LDAP protocol analyzer from the zeek/spicy-ldap + project (https://github.com/zeek/spicy-ldap). This analyzer is enabled by + default. The analyzer's events and its ``ldap.log`` and ``ldap_search.log`` + should be considered preliminary and experimental until the arrival of + Zeek's next long-term-stable release (7.0). + + If you observe unusually high CPU consumption or other issues due to this + analyzer being enabled by default, the easiest way to disable it is via + the ``Analyzer::disabled_analyzers`` const as follows: + + redef Analyzer::disabled_analyzers += { + Analyzer::ANALYZER_LDAP_UDP, + Analyzer::ANALYZER_LDAP_TCP, + }; + + Please do report issues to us including diagnostic information in case this is + necessary in your environment. We're also open to general feedback about the + structure of the new logs. + - Added a new ``assert`` statement for assertion based testing and asserting runtime state. diff --git a/VERSION b/VERSION index 8af5d0506c..0972e3de71 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.1.0-dev.502 +6.1.0-dev.528 diff --git a/scripts/base/init-default.zeek b/scripts/base/init-default.zeek index cbfcca571d..b7d11dfbd2 100644 --- a/scripts/base/init-default.zeek +++ b/scripts/base/init-default.zeek @@ -59,6 +59,7 @@ @load base/protocols/imap @load base/protocols/irc @load base/protocols/krb +@load base/protocols/ldap @load base/protocols/modbus @load base/protocols/mqtt @load base/protocols/mysql diff --git a/scripts/base/protocols/ldap/__load__.zeek b/scripts/base/protocols/ldap/__load__.zeek new file mode 100644 index 0000000000..7f84910034 --- /dev/null +++ b/scripts/base/protocols/ldap/__load__.zeek @@ -0,0 +1,5 @@ +@if ( have_spicy_analyzers() ) +@load-sigs ./dpd.sig +@load ./consts +@load ./main.zeek +@endif diff --git a/scripts/base/protocols/ldap/consts.zeek b/scripts/base/protocols/ldap/consts.zeek new file mode 100644 index 0000000000..bbd378c7e8 --- /dev/null +++ b/scripts/base/protocols/ldap/consts.zeek @@ -0,0 +1,123 @@ +module LDAP; + +export { + const PROTOCOL_OPCODES = { [ LDAP::ProtocolOpcode_BIND_REQUEST ] = "bind", [ + LDAP::ProtocolOpcode_BIND_RESPONSE ] = "bind", [ + LDAP::ProtocolOpcode_UNBIND_REQUEST ] = "unbind", [ + LDAP::ProtocolOpcode_SEARCH_REQUEST ] = "search", [ + LDAP::ProtocolOpcode_SEARCH_RESULT_ENTRY ] = "search", [ + LDAP::ProtocolOpcode_SEARCH_RESULT_DONE ] = "search", [ + LDAP::ProtocolOpcode_MODIFY_REQUEST ] = "modify", [ + LDAP::ProtocolOpcode_MODIFY_RESPONSE ] = "modify", [ + LDAP::ProtocolOpcode_ADD_REQUEST ] = "add", [ + LDAP::ProtocolOpcode_ADD_RESPONSE ] = "add", [ + LDAP::ProtocolOpcode_DEL_REQUEST ] = "delete", [ + LDAP::ProtocolOpcode_DEL_RESPONSE ] = "delete", [ + LDAP::ProtocolOpcode_MOD_DN_REQUEST ] = "modify", [ + LDAP::ProtocolOpcode_MOD_DN_RESPONSE ] = "modify", [ + LDAP::ProtocolOpcode_COMPARE_REQUEST ] = "compare", [ + LDAP::ProtocolOpcode_COMPARE_RESPONSE ] = "compare", [ + LDAP::ProtocolOpcode_ABANDON_REQUEST ] = "abandon", [ + LDAP::ProtocolOpcode_SEARCH_RESULT_REFERENCE ] = "search", [ + LDAP::ProtocolOpcode_EXTENDED_REQUEST ] = "extended", [ + LDAP::ProtocolOpcode_EXTENDED_RESPONSE ] = "extended", [ + LDAP::ProtocolOpcode_INTERMEDIATE_RESPONSE ] = "intermediate" } + &default="unknown"; + + const BIND_SIMPLE = "bind simple"; + const BIND_SASL = "bind SASL"; + + const RESULT_CODES = { [ LDAP::ResultCode_SUCCESS ] = "success", [ + LDAP::ResultCode_OPERATIONS_ERROR ] = "operations error", [ + LDAP::ResultCode_PROTOCOL_ERROR ] = "protocol error", [ + LDAP::ResultCode_TIME_LIMIT_EXCEEDED ] = "time limit exceeded", [ + LDAP::ResultCode_SIZE_LIMIT_EXCEEDED ] = "size limit exceeded", [ + LDAP::ResultCode_COMPARE_FALSE ] = "compare false", [ + LDAP::ResultCode_COMPARE_TRUE ] = "compare true", [ + LDAP::ResultCode_AUTH_METHOD_NOT_SUPPORTED ] = + "auth method not supported", [ + LDAP::ResultCode_STRONGER_AUTH_REQUIRED ] = + "stronger auth required", [ LDAP::ResultCode_PARTIAL_RESULTS ] = + "partial results", [ LDAP::ResultCode_REFERRAL ] = "referral", [ + LDAP::ResultCode_ADMIN_LIMIT_EXCEEDED ] = "admin limit exceeded", [ + LDAP::ResultCode_UNAVAILABLE_CRITICAL_EXTENSION ] = + "unavailable critical extension", [ + LDAP::ResultCode_CONFIDENTIALITY_REQUIRED ] = + "confidentiality required", [ LDAP::ResultCode_SASL_BIND_IN_PROGRESS ] = + "SASL bind in progress", [ LDAP::ResultCode_NO_SUCH_ATTRIBUTE ] = + "no such attribute", [ LDAP::ResultCode_UNDEFINED_ATTRIBUTE_TYPE ] = + "undefined attribute type", [ + LDAP::ResultCode_INAPPROPRIATE_MATCHING ] = + "inappropriate matching", [ LDAP::ResultCode_CONSTRAINT_VIOLATION ] = + "constraint violation", [ LDAP::ResultCode_ATTRIBUTE_OR_VALUE_EXISTS ] = + "attribute or value exists", [ + LDAP::ResultCode_INVALID_ATTRIBUTE_SYNTAX ] = + "invalid attribute syntax", [ LDAP::ResultCode_NO_SUCH_OBJECT ] = + "no such object", [ LDAP::ResultCode_ALIAS_PROBLEM ] = + "alias problem", [ LDAP::ResultCode_INVALID_DNSYNTAX ] = + "invalid DN syntax", [ LDAP::ResultCode_ALIAS_DEREFERENCING_PROBLEM ] = + "alias dereferencing problem", [ + LDAP::ResultCode_INAPPROPRIATE_AUTHENTICATION ] = + "inappropriate authentication", [ + LDAP::ResultCode_INVALID_CREDENTIALS ] = "invalid credentials", [ + LDAP::ResultCode_INSUFFICIENT_ACCESS_RIGHTS ] = + "insufficient access rights", [ LDAP::ResultCode_BUSY ] = "busy", [ + LDAP::ResultCode_UNAVAILABLE ] = "unavailable", [ + LDAP::ResultCode_UNWILLING_TO_PERFORM ] = "unwilling to perform", [ + LDAP::ResultCode_LOOP_DETECT ] = "loop detect", [ + LDAP::ResultCode_SORT_CONTROL_MISSING ] = "sort control missing", [ + LDAP::ResultCode_OFFSET_RANGE_ERROR ] = "offset range error", [ + LDAP::ResultCode_NAMING_VIOLATION ] = "naming violation", [ + LDAP::ResultCode_OBJECT_CLASS_VIOLATION ] = + "object class violation", [ LDAP::ResultCode_NOT_ALLOWED_ON_NON_LEAF ] = + "not allowed on non-leaf", [ LDAP::ResultCode_NOT_ALLOWED_ON_RDN ] = + "not allowed on RDN", [ LDAP::ResultCode_ENTRY_ALREADY_EXISTS ] = + "entry already exists", [ + LDAP::ResultCode_OBJECT_CLASS_MODS_PROHIBITED ] = + "object class mods prohibited", [ LDAP::ResultCode_RESULTS_TOO_LARGE ] = + "results too large", [ LDAP::ResultCode_AFFECTS_MULTIPLE_DSAS ] = + "affects multiple DSAs", [ LDAP::ResultCode_CONTROL_ERROR ] = + "control error", [ LDAP::ResultCode_OTHER ] = "other", [ + LDAP::ResultCode_SERVER_DOWN ] = "server down", [ + LDAP::ResultCode_LOCAL_ERROR ] = "local error", [ + LDAP::ResultCode_ENCODING_ERROR ] = "encoding error", [ + LDAP::ResultCode_DECODING_ERROR ] = "decoding error", [ + LDAP::ResultCode_TIMEOUT ] = "timeout", [ + LDAP::ResultCode_AUTH_UNKNOWN ] = "auth unknown", [ + LDAP::ResultCode_FILTER_ERROR ] = "filter error", [ + LDAP::ResultCode_USER_CANCELED ] = "user canceled", [ + LDAP::ResultCode_PARAM_ERROR ] = "param error", [ + LDAP::ResultCode_NO_MEMORY ] = "no memory", [ + LDAP::ResultCode_CONNECT_ERROR ] = "connect error", [ + LDAP::ResultCode_NOT_SUPPORTED ] = "not supported", [ + LDAP::ResultCode_CONTROL_NOT_FOUND ] = "control not found", [ + LDAP::ResultCode_NO_RESULTS_RETURNED ] = "no results returned", [ + LDAP::ResultCode_MORE_RESULTS_TO_RETURN ] = + "more results to return", [ LDAP::ResultCode_CLIENT_LOOP ] = + "client loop", [ LDAP::ResultCode_REFERRAL_LIMIT_EXCEEDED ] = + "referral limit exceeded", [ LDAP::ResultCode_INVALID_RESPONSE ] = + "invalid response", [ LDAP::ResultCode_AMBIGUOUS_RESPONSE ] = + "ambiguous response", [ LDAP::ResultCode_TLS_NOT_SUPPORTED ] = + "TLS not supported", [ LDAP::ResultCode_INTERMEDIATE_RESPONSE ] = + "intermediate response", [ LDAP::ResultCode_UNKNOWN_TYPE ] = + "unknown type", [ LDAP::ResultCode_LCUP_INVALID_DATA ] = + "LCUP invalid data", [ LDAP::ResultCode_LCUP_UNSUPPORTED_SCHEME ] = + "LCUP unsupported scheme", [ LDAP::ResultCode_LCUP_RELOAD_REQUIRED ] = + "LCUP reload required", [ LDAP::ResultCode_CANCELED ] = + "canceled", [ LDAP::ResultCode_NO_SUCH_OPERATION ] = + "no such operation", [ LDAP::ResultCode_TOO_LATE ] = "too late", [ + LDAP::ResultCode_CANNOT_CANCEL ] = "cannot cancel", [ + LDAP::ResultCode_ASSERTION_FAILED ] = "assertion failed", [ + LDAP::ResultCode_AUTHORIZATION_DENIED ] = "authorization denied" } + &default="unknown"; + + const SEARCH_SCOPES = { [ LDAP::SearchScope_SEARCH_BASE ] = "base", [ + LDAP::SearchScope_SEARCH_SINGLE ] = "single", [ + LDAP::SearchScope_SEARCH_TREE ] = "tree", } &default="unknown"; + + const SEARCH_DEREF_ALIASES = { [ LDAP::SearchDerefAlias_DEREF_NEVER ] = + "never", [ LDAP::SearchDerefAlias_DEREF_IN_SEARCHING ] = + "searching", [ LDAP::SearchDerefAlias_DEREF_FINDING_BASE ] = + "finding", [ LDAP::SearchDerefAlias_DEREF_ALWAYS ] = "always", } + &default="unknown"; +} diff --git a/scripts/base/protocols/ldap/dpd.sig b/scripts/base/protocols/ldap/dpd.sig new file mode 100644 index 0000000000..bf545ed375 --- /dev/null +++ b/scripts/base/protocols/ldap/dpd.sig @@ -0,0 +1,23 @@ +signature dpd_ldap_client_udp { + ip-proto == udp + payload /^\x30.\x02\x01.\x60/ +} + +signature dpd_ldap_server_udp { + ip-proto == udp + payload /^\x30/ + requires-reverse-signature dpd_ldap_client_udp + enable "LDAP_UDP" +} + +signature dpd_ldap_client_tcp { + ip-proto == tcp + payload /^\x30.\x02\x01.\x60/ +} + +signature dpd_ldap_server_tcp { + ip-proto == tcp + payload /^\x30/ + requires-reverse-signature dpd_ldap_client_tcp + enable "LDAP_TCP" +} diff --git a/scripts/base/protocols/ldap/main.zeek b/scripts/base/protocols/ldap/main.zeek new file mode 100644 index 0000000000..2c05020ddf --- /dev/null +++ b/scripts/base/protocols/ldap/main.zeek @@ -0,0 +1,368 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +@load base/protocols/conn/removal-hooks + +@load ./consts + +module LDAP; + +export { + redef enum Log::ID += { LDAP_LOG, LDAP_SEARCH_LOG }; + + ## TCP ports which should be considered for analysis. + const ports_tcp = { 389/tcp, 3268/tcp } &redef; + + ## UDP ports which should be considered for analysis. + const ports_udp = { 389/udp } &redef; + + ## Whether clear text passwords are captured or not. + option default_capture_password = F; + + ## Whether to log LDAP search attributes or not. + option default_log_search_attributes = F; + + ## Default logging policy hook for LDAP_LOG. + global log_policy: Log::PolicyHook; + + ## Default logging policy hook for LDAP_SEARCH_LOG. + global log_policy_search: Log::PolicyHook; + + ## LDAP finalization hook. + global finalize_ldap: Conn::RemovalHook; + + ############################################################################# + # This is the format of ldap.log (ldap operations minus search-related) + # Each line represents a unique connection+message_id (requests/responses) + type MessageInfo: 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; + + # Message ID + message_id: int &log &optional; + + # LDAP version + version: int &log &optional; + + # normalized operations (e.g., bind_request and bind_response to "bind") + opcodes: set[string] &log &optional; + + # Result code(s) + results: set[string] &log &optional; + + # result diagnostic message(s) + diagnostic_messages: vector of string &log &optional; + + # object(s) + objects: vector of string &log &optional; + + # argument(s) + arguments: vector of string &log &optional; + }; + + ############################################################################# + # This is the format of ldap_search.log (search-related messages only) + # Each line represents a unique connection+message_id (requests/responses) + type SearchInfo: 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; + + # Message ID + message_id: int &log &optional; + + # sets of search scope and deref alias + scopes: set[string] &log &optional; + derefs: set[string] &log &optional; + + # base search objects + base_objects: vector of string &log &optional; + + # number of results returned + result_count: count &log &optional; + + # Result code (s) + results: set[string] &log &optional; + + # result diagnostic message(s) + diagnostic_messages: vector of string &log &optional; + + # a string representation of the search filter used in the query + filter: string &log &optional; + + # a list of attributes that were returned in the search + attributes: vector of string &log &optional; + }; + + type State: record { + messages: table[int] of MessageInfo &optional; + searches: table[int] of SearchInfo &optional; + }; + + # Event that can be handled to access the ldap record as it is sent on + # to the logging framework. + global log_ldap: event(rec: LDAP::MessageInfo); + global log_ldap_search: event(rec: LDAP::SearchInfo); + + # Event called for each LDAP message (either direction) + global LDAP::message: event(c: connection, + message_id: int, + opcode: LDAP::ProtocolOpcode, + result: LDAP::ResultCode, + matched_dn: string, + diagnostic_message: string, + object: string, + argument: string); +} + +redef record connection += { + ldap: State &optional; +}; + +redef likely_server_ports += { LDAP::ports_tcp, LDAP::ports_udp }; + +############################################################################# +global OPCODES_FINISHED: set[LDAP::ProtocolOpcode] = { LDAP::ProtocolOpcode_BIND_RESPONSE, + LDAP::ProtocolOpcode_UNBIND_REQUEST, + LDAP::ProtocolOpcode_SEARCH_RESULT_DONE, + LDAP::ProtocolOpcode_MODIFY_RESPONSE, + LDAP::ProtocolOpcode_ADD_RESPONSE, + LDAP::ProtocolOpcode_DEL_RESPONSE, + LDAP::ProtocolOpcode_MOD_DN_RESPONSE, + LDAP::ProtocolOpcode_COMPARE_RESPONSE, + LDAP::ProtocolOpcode_ABANDON_REQUEST, + LDAP::ProtocolOpcode_EXTENDED_RESPONSE }; + +global OPCODES_SEARCH: set[LDAP::ProtocolOpcode] = { LDAP::ProtocolOpcode_SEARCH_REQUEST, + LDAP::ProtocolOpcode_SEARCH_RESULT_ENTRY, + LDAP::ProtocolOpcode_SEARCH_RESULT_DONE, + LDAP::ProtocolOpcode_SEARCH_RESULT_REFERENCE }; + +############################################################################# +event zeek_init() &priority=5 { + Analyzer::register_for_ports(Analyzer::ANALYZER_LDAP_TCP, LDAP::ports_tcp); + Analyzer::register_for_ports(Analyzer::ANALYZER_LDAP_UDP, LDAP::ports_udp); + + Log::create_stream(LDAP::LDAP_LOG, [$columns=MessageInfo, $ev=log_ldap, $path="ldap", $policy=log_policy]); + Log::create_stream(LDAP::LDAP_SEARCH_LOG, [$columns=SearchInfo, $ev=log_ldap_search, $path="ldap_search", $policy=log_policy_search]); +} + +############################################################################# +function set_session(c: connection, message_id: int, opcode: LDAP::ProtocolOpcode) { + + if (! c?$ldap ) { + c$ldap = State(); + Conn::register_removal_hook(c, finalize_ldap); + } + + if (! c$ldap?$messages ) + c$ldap$messages = table(); + + if (! c$ldap?$searches ) + c$ldap$searches = table(); + + if ((opcode in OPCODES_SEARCH) && (message_id !in c$ldap$searches)) { + c$ldap$searches[message_id] = [$ts=network_time(), + $uid=c$uid, + $id=c$id, + $message_id=message_id, + $result_count=0]; + + } else if ((opcode !in OPCODES_SEARCH) && (message_id !in c$ldap$messages)) { + c$ldap$messages[message_id] = [$ts=network_time(), + $uid=c$uid, + $id=c$id, + $message_id=message_id]; + } +} + +############################################################################# +event LDAP::message(c: connection, + message_id: int, + opcode: LDAP::ProtocolOpcode, + result: LDAP::ResultCode, + matched_dn: string, + diagnostic_message: string, + object: string, + argument: string) { + + if (opcode == LDAP::ProtocolOpcode_SEARCH_RESULT_DONE) { + set_session(c, message_id, opcode); + + local searches = c$ldap$searches[message_id]; + + if ( result != LDAP::ResultCode_Undef ) { + if ( ! searches?$results ) + searches$results = set(); + add searches$results[RESULT_CODES[result]]; + } + + if ( diagnostic_message != "" ) { + if ( ! searches?$diagnostic_messages ) + searches$diagnostic_messages = vector(); + searches$diagnostic_messages += diagnostic_message; + } + + Log::write(LDAP::LDAP_SEARCH_LOG, searches); + delete c$ldap$searches[message_id]; + + } else if (opcode !in OPCODES_SEARCH) { + set_session(c, message_id, opcode); + + local messages = c$ldap$messages[message_id]; + + if ( ! messages?$opcodes ) + messages$opcodes = set(); + add messages$opcodes[PROTOCOL_OPCODES[opcode]]; + + if ( result != LDAP::ResultCode_Undef ) { + if ( ! messages?$results ) + messages$results = set(); + add messages$results[RESULT_CODES[result]]; + } + + if ( diagnostic_message != "" ) { + if ( ! messages?$diagnostic_messages ) + messages$diagnostic_messages = vector(); + messages$diagnostic_messages += diagnostic_message; + } + + if ( object != "" ) { + if ( ! messages?$objects ) + messages$objects = vector(); + messages$objects += object; + } + + if ( argument != "" ) { + if ( ! messages?$arguments ) + messages$arguments = vector(); + if ("bind simple" in messages$opcodes && !default_capture_password) + messages$arguments += "REDACTED"; + else + messages$arguments += argument; + } + + if (opcode in OPCODES_FINISHED) { + + if ((BIND_SIMPLE in messages$opcodes) || + (BIND_SASL in messages$opcodes)) { + # don't have both "bind" and "bind " in the operations list + delete messages$opcodes[PROTOCOL_OPCODES[LDAP::ProtocolOpcode_BIND_REQUEST]]; + } + + Log::write(LDAP::LDAP_LOG, messages); + delete c$ldap$messages[message_id]; + } + } + +} + +############################################################################# +event LDAP::searchreq(c: connection, + message_id: int, + base_object: string, + scope: LDAP::SearchScope, + deref: LDAP::SearchDerefAlias, + size_limit: int, + time_limit: int, + types_only: bool, + filter: string, + attributes: vector of string) { + + set_session(c, message_id, LDAP::ProtocolOpcode_SEARCH_REQUEST); + + if ( scope != LDAP::SearchScope_Undef ) { + if ( ! c$ldap$searches[message_id]?$scopes ) + c$ldap$searches[message_id]$scopes = set(); + add c$ldap$searches[message_id]$scopes[SEARCH_SCOPES[scope]]; + } + + if ( deref != LDAP::SearchDerefAlias_Undef ) { + if ( ! c$ldap$searches[message_id]?$derefs ) + c$ldap$searches[message_id]$derefs = set(); + add c$ldap$searches[message_id]$derefs[SEARCH_DEREF_ALIASES[deref]]; + } + + if ( base_object != "" ) { + if ( ! c$ldap$searches[message_id]?$base_objects ) + c$ldap$searches[message_id]$base_objects = vector(); + c$ldap$searches[message_id]$base_objects += base_object; + } + c$ldap$searches[message_id]$filter = filter; + + if ( default_log_search_attributes ) { + c$ldap$searches[message_id]$attributes = attributes; + } +} + +############################################################################# +event LDAP::searchres(c: connection, + message_id: int, + object_name: string) { + + set_session(c, message_id, LDAP::ProtocolOpcode_SEARCH_RESULT_ENTRY); + + c$ldap$searches[message_id]$result_count += 1; +} + +############################################################################# +event LDAP::bindreq(c: connection, + message_id: int, + version: int, + name: string, + authType: LDAP::BindAuthType, + authInfo: string) { + set_session(c, message_id, LDAP::ProtocolOpcode_BIND_REQUEST); + + if ( ! c$ldap$messages[message_id]?$version ) + c$ldap$messages[message_id]$version = version; + + if ( ! c$ldap$messages[message_id]?$opcodes ) + c$ldap$messages[message_id]$opcodes = set(); + + if (authType == LDAP::BindAuthType_BIND_AUTH_SIMPLE) { + add c$ldap$messages[message_id]$opcodes[BIND_SIMPLE]; + } else if (authType == LDAP::BindAuthType_BIND_AUTH_SASL) { + add c$ldap$messages[message_id]$opcodes[BIND_SASL]; + } +} + +############################################################################# +hook finalize_ldap(c: connection) { + # log any "pending" unlogged LDAP messages/searches + + if ( c$ldap?$messages && (|c$ldap$messages| > 0) ) { + for ( [mid], m in c$ldap$messages ) { + if (mid > 0) { + + if ((BIND_SIMPLE in m$opcodes) || (BIND_SASL in m$opcodes)) { + # don't have both "bind" and "bind " in the operations list + delete m$opcodes[PROTOCOL_OPCODES[LDAP::ProtocolOpcode_BIND_REQUEST]]; + } + + Log::write(LDAP::LDAP_LOG, m); + } + } + delete c$ldap$messages; + } + + if ( c$ldap?$searches && (|c$ldap$searches| > 0) ) { + for ( [mid], s in c$ldap$searches ) { + if (mid > 0) { + Log::write(LDAP::LDAP_SEARCH_LOG, s); + } + } + delete c$ldap$searches; + } + +} diff --git a/src/analyzer/protocol/CMakeLists.txt b/src/analyzer/protocol/CMakeLists.txt index 4ea6642255..314a965730 100644 --- a/src/analyzer/protocol/CMakeLists.txt +++ b/src/analyzer/protocol/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(ident) add_subdirectory(imap) add_subdirectory(irc) add_subdirectory(krb) +add_subdirectory(ldap) add_subdirectory(login) add_subdirectory(mime) add_subdirectory(modbus) diff --git a/src/analyzer/protocol/ldap/CMakeLists.txt b/src/analyzer/protocol/ldap/CMakeLists.txt new file mode 100644 index 0000000000..a687e880ff --- /dev/null +++ b/src/analyzer/protocol/ldap/CMakeLists.txt @@ -0,0 +1,5 @@ +spicy_add_analyzer( + NAME LDAP + PACKAGE_NAME spicy-ldap + SOURCES ldap.spicy ldap.evt asn1.spicy + MODULES LDAP ASN1) diff --git a/src/analyzer/protocol/ldap/asn1.spicy b/src/analyzer/protocol/ldap/asn1.spicy new file mode 100644 index 0000000000..001c131e25 --- /dev/null +++ b/src/analyzer/protocol/ldap/asn1.spicy @@ -0,0 +1,278 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +module ASN1; + +############################################################################### +# ASN.1 structure decoding +# +# A Layman's Guide to a Subset of ASN.1, BER, and DER +# http://luca.ntop.org/Teaching/Appunti/asn1.html +# +# ASN.1 Tutorial from Computer Networks and Open Systems: +# An Application Development Perspective +# https://www.obj-sys.com/asn1tutorial/asn1only.html +# +# The ASN1JS tool (http://lapo.it/asn1js and https://github.com/lapo-luchini/asn1js) +# is invaluable in debugging ASN.1 +############################################################################### + +import spicy; + +#- ASN.1 data types ---------------------------------------------------------- +# https://www.obj-sys.com/asn1tutorial/node124.html +# https://www.obj-sys.com/asn1tutorial/node10.html + +public type ASN1Type = enum { + Boolean = 1, + Integer = 2, + BitString = 3, + OctetString = 4, + NullVal = 5, + ObjectIdentifier = 6, + ObjectDescriptor = 7, + InstanceOf = 8, + Real = 9, + Enumerated = 10, + EmbeddedPDV = 11, + UTF8String = 12, + RelativeOID = 13, + Sequence = 16, + Set = 17, + NumericString = 18, + PrintableString = 19, + TeletextString = 20, + VideotextString = 21, + IA5String = 22, + UTCTime = 23, + GeneralizedTime = 24, + GraphicString = 25, + VisibleString = 26, + GeneralString = 27, + UniversalString = 28, + CharacterString = 29, + BMPString = 30 +}; + +#- ASN.1 data classes -------------------------------------------------------- + +public type ASN1Class = enum { + Universal = 0, + Application = 1, + ContextSpecific = 2, + Private = 3 +}; + +#- ASN.1 tag definition (including length) ------------------------------------ + +type LengthType = unit { + var len: uint64; + var tag_len: uint8; + + data : bitfield(8) { + num: 0..6; + islong: 7; + }; + + + switch ( self.data.islong ) { + 0 -> : void { + self.len = self.data.num; + self.tag_len = 1; + } + 1 -> : bytes &size=self.data.num + &convert=$$.to_uint(spicy::ByteOrder::Network) { + self.len = $$; + self.tag_len = self.data.num + 1; + } + }; +}; + +type ASN1Tag = unit { + : bitfield(8) { + type_: 0..4 &convert=ASN1Type($$); + constructed: 5 &convert=cast($$); + class: 6..7 &convert=ASN1Class($$); + }; +}; + +#- ASN.1 bit string ----------------------------------------------------------- +# https://www.obj-sys.com/asn1tutorial/node10.html + +type ASN1BitString = unit(len: uint64, constructed: bool) { + : uint8; # unused bits + value_bits: bytes &size=(len - 1); + + # TODO - constructed form + # https://github.com/zeek/spicy/issues/921 + # `bytes` needs << and >> support before we can implement complex bitstrings + # +}; + +#- ASN.1 octet string --------------------------------------------------------- +# https://www.obj-sys.com/asn1tutorial/node10.html + +type ASN1OctetString = unit(len: uint64, constructed: bool) { + value: bytes &size = len; + + # TODO - constructed form +}; + +#- ASN.1 various string types ------------------------------------------------- +# https://www.obj-sys.com/asn1tutorial/node124.html + +type ASN1String = unit(tag: ASN1Tag, len: uint64) { + var encoding: hilti::Charset; + + on %init { + switch ( tag.type_ ) { + # see "Restricted Character String Types" in + # "Generic String Encoding Rules (GSER) for ASN.1 Types" + # (https://datatracker.ietf.org/doc/html/rfc3641#section-3.2) + + case ASN1Type::PrintableString, + ASN1Type::GeneralizedTime, + ASN1Type::UTCTime: { + self.encoding = hilti::Charset::ASCII; + } + + case ASN1Type::UTF8String, + ASN1Type::GeneralString, + ASN1Type::CharacterString, + ASN1Type::GraphicString, + ASN1Type::IA5String, + ASN1Type::NumericString, + ASN1Type::TeletextString, + ASN1Type::VideotextString, + ASN1Type::VisibleString, + # TODO: RFC3641 mentions special UTF-8 mapping rules for + # BMPString and UniversalString. This *may* not be correct. + ASN1Type::BMPString, + ASN1Type::UniversalString: { + self.encoding = hilti::Charset::UTF8; + } + } + } + + value: ASN1OctetString(len, tag.constructed) &convert=$$.value.decode(self.encoding); +} &convert=self.value; + +#- ASN.1 OID ------------------------------------------------------------------ +# https://www.obj-sys.com/asn1tutorial/node124.html + +type ASN1ObjectIdentifierNibble = unit { + data : bitfield(8) { + num: 0..6; + more: 7; + }; +} &convert=self.data; + +type ASN1ObjectIdentifier = unit(len: uint64) { + var oid: vector; + var temp: uint64; + var oidstring: string; + + : uint8 if ( len >= 1 ) { + self.temp = $$ / 40; + self.oid.push_back( self.temp ); + self.oidstring = "%d" % (self.temp); + self.temp = $$ % 40; + self.oid.push_back( self.temp ); + self.oidstring = self.oidstring + ".%d" % (self.temp); + self.temp = 0; + } + + sublist: ASN1ObjectIdentifierNibble[len - 1] foreach { + self.temp = ( self.temp<<7 ) | $$.num; + if ( $$.more != 1 ) { + self.oid.push_back(self.temp); + self.oidstring = self.oidstring + ".%d" % (self.temp); + self.temp = 0; + } + } +}; + + +#- ASN.1 message header (tag + length information) ---------------------------- + +public type ASN1Header = unit { + tag: ASN1Tag; + len: LengthType; +}; + +#- ASN.1 message body --------------------------------------------------------- + +public type ASN1Body = unit(head: ASN1Header, recursive: bool) { + switch ( head.tag.type_ ) { + + ASN1Type::Boolean -> bool_value: uint8 &convert=cast($$) &requires=head.len.len==1; + + ASN1Type::Integer, + ASN1Type::Enumerated -> num_value: bytes &size=head.len.len + &convert=$$.to_int(spicy::ByteOrder::Big); + + ASN1Type::NullVal -> null_value: bytes &size=0 &requires=head.len.len==0; + + ASN1Type::BitString -> bitstr_value: ASN1BitString(head.len.len, head.tag.constructed); + + ASN1Type::OctetString -> str_value: ASN1OctetString(head.len.len, head.tag.constructed) + &convert=$$.value.decode(hilti::Charset::ASCII); + + ASN1Type::ObjectIdentifier -> str_value: ASN1ObjectIdentifier(head.len.len) + &convert=$$.oidstring; + + ASN1Type::BMPString, + ASN1Type::CharacterString, + ASN1Type::GeneralizedTime, + ASN1Type::GeneralString, + ASN1Type::GraphicString, + ASN1Type::IA5String, + ASN1Type::NumericString, + ASN1Type::PrintableString, + ASN1Type::TeletextString, + ASN1Type::UTCTime, + ASN1Type::UTF8String, + ASN1Type::VideotextString, + ASN1Type::VisibleString, + ASN1Type::UniversalString -> str_value: ASN1String(head.tag, head.len.len); + + ASN1Type::Sequence, ASN1Type::Set -> seq: ASN1SubMessages(head.len.len) if (recursive); + + # TODO: ASN1Type values not handled yet + ASN1Type::ObjectDescriptor, + ASN1Type::InstanceOf, + ASN1Type::Real, + ASN1Type::EmbeddedPDV, + ASN1Type::RelativeOID -> unimplemented_value: bytes &size=head.len.len; + + # unknown (to me) ASN.1 enumeration, skip over silently + * -> unimplemented_value: bytes &size=head.len.len; + }; +}; + +#- ASN.1 array of ASN.1 sequence/set sub-messages (up to msgLen bytes) -------- + +public type ASN1SubMessages = unit(msgLen: uint64) { + submessages: ASN1Message(True)[] &eod; +} &size=msgLen; + +#- ASN.1 message with header and body ----------------------------------------- +# Universal or Application/ContextSpecific/Private +# - if Universal, body:ASN1Body is parsed +# - else, application_data:bytes stores data array + +public type ASN1Message = unit(recursive: bool) { + var application_id: int32; + + head: ASN1Header; + switch ( self.head.tag.class ) { + + ASN1Class::Universal -> body: ASN1Body(self.head, recursive); + + ASN1Class::Application, + ASN1Class::ContextSpecific, + ASN1Class::Private -> application_data: bytes &size=self.head.len.len { + self.application_id = cast(self.head.tag.type_); + } + + }; +}; diff --git a/src/analyzer/protocol/ldap/ldap.evt b/src/analyzer/protocol/ldap/ldap.evt new file mode 100644 index 0000000000..c0fc25cdb0 --- /dev/null +++ b/src/analyzer/protocol/ldap/ldap.evt @@ -0,0 +1,40 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +protocol analyzer LDAP_TCP over TCP: + parse with LDAP::Messages; + +protocol analyzer LDAP_UDP over UDP: + parse with LDAP::Messages; + +import LDAP; + +on LDAP::Message -> event LDAP::message($conn, + self.messageID, + self.opcode, + self.result.code, + self.result.matchedDN, + self.result.diagnosticMessage, + self.obj, + self.arg); + +on LDAP::BindRequest -> event LDAP::bindreq($conn, + message.messageID, + self.version, + self.name, + self.authType, + message.arg); + +on LDAP::SearchRequest -> event LDAP::searchreq($conn, + message.messageID, + self.baseObject, + self.scope, + self.deref, + self.sizeLimit, + self.timeLimit, + self.typesOnly, + self.filter, + self.attributes); + +on LDAP::SearchResultEntry -> event LDAP::searchres($conn, + message.messageID, + self.objectName); diff --git a/src/analyzer/protocol/ldap/ldap.spicy b/src/analyzer/protocol/ldap/ldap.spicy new file mode 100644 index 0000000000..ee6dc74db9 --- /dev/null +++ b/src/analyzer/protocol/ldap/ldap.spicy @@ -0,0 +1,875 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +module LDAP; + +import ASN1; +import spicy; + +# https://tools.ietf.org/html/rfc4511# +# https://ldap.com/ldapv3-wire-protocol-reference-asn1-ber/ +# https://lapo.it/asn1js + +#- Operation opcode ---------------------------------------------------------- +public type ProtocolOpcode = enum { + BIND_REQUEST = 0, + BIND_RESPONSE = 1, + UNBIND_REQUEST = 2, + SEARCH_REQUEST = 3, + SEARCH_RESULT_ENTRY = 4, + SEARCH_RESULT_DONE = 5, + MODIFY_REQUEST = 6, + MODIFY_RESPONSE = 7, + ADD_REQUEST = 8, + ADD_RESPONSE = 9, + DEL_REQUEST = 10, + DEL_RESPONSE = 11, + MOD_DN_REQUEST = 12, + MOD_DN_RESPONSE = 13, + COMPARE_REQUEST = 14, + COMPARE_RESPONSE = 15, + ABANDON_REQUEST = 16, + SEARCH_RESULT_REFERENCE = 19, + EXTENDED_REQUEST = 23, + EXTENDED_RESPONSE = 24, + INTERMEDIATE_RESPONSE = 25, +}; + +#- Result code --------------------------------------------------------------- +public type ResultCode = enum { + SUCCESS = 0, + OPERATIONS_ERROR = 1, + PROTOCOL_ERROR = 2, + TIME_LIMIT_EXCEEDED = 3, + SIZE_LIMIT_EXCEEDED = 4, + COMPARE_FALSE = 5, + COMPARE_TRUE = 6, + AUTH_METHOD_NOT_SUPPORTED = 7, + STRONGER_AUTH_REQUIRED = 8, + PARTIAL_RESULTS = 9, + REFERRAL = 10, + ADMIN_LIMIT_EXCEEDED = 11, + UNAVAILABLE_CRITICAL_EXTENSION = 12, + CONFIDENTIALITY_REQUIRED = 13, + SASL_BIND_IN_PROGRESS = 14, + NO_SUCH_ATTRIBUTE = 16, + UNDEFINED_ATTRIBUTE_TYPE = 17, + INAPPROPRIATE_MATCHING = 18, + CONSTRAINT_VIOLATION = 19, + ATTRIBUTE_OR_VALUE_EXISTS = 20, + INVALID_ATTRIBUTE_SYNTAX = 21, + NO_SUCH_OBJECT = 32, + ALIAS_PROBLEM = 33, + INVALID_DNSYNTAX = 34, + ALIAS_DEREFERENCING_PROBLEM = 36, + INAPPROPRIATE_AUTHENTICATION = 48, + INVALID_CREDENTIALS = 49, + INSUFFICIENT_ACCESS_RIGHTS = 50, + BUSY = 51, + UNAVAILABLE = 52, + UNWILLING_TO_PERFORM = 53, + LOOP_DETECT = 54, + SORT_CONTROL_MISSING = 60, + OFFSET_RANGE_ERROR = 61, + NAMING_VIOLATION = 64, + OBJECT_CLASS_VIOLATION = 65, + NOT_ALLOWED_ON_NON_LEAF = 66, + NOT_ALLOWED_ON_RDN = 67, + ENTRY_ALREADY_EXISTS = 68, + OBJECT_CLASS_MODS_PROHIBITED = 69, + RESULTS_TOO_LARGE = 70, + AFFECTS_MULTIPLE_DSAS = 71, + CONTROL_ERROR = 76, + OTHER = 80, + SERVER_DOWN = 81, + LOCAL_ERROR = 82, + ENCODING_ERROR = 83, + DECODING_ERROR = 84, + TIMEOUT = 85, + AUTH_UNKNOWN = 86, + FILTER_ERROR = 87, + USER_CANCELED = 88, + PARAM_ERROR = 89, + NO_MEMORY = 90, + CONNECT_ERROR = 91, + NOT_SUPPORTED = 92, + CONTROL_NOT_FOUND = 93, + NO_RESULTS_RETURNED = 94, + MORE_RESULTS_TO_RETURN = 95, + CLIENT_LOOP = 96, + REFERRAL_LIMIT_EXCEEDED = 97, + INVALID_RESPONSE = 100, + AMBIGUOUS_RESPONSE = 101, + TLS_NOT_SUPPORTED = 112, + INTERMEDIATE_RESPONSE = 113, + UNKNOWN_TYPE = 114, + LCUP_INVALID_DATA = 115, + LCUP_UNSUPPORTED_SCHEME = 116, + LCUP_RELOAD_REQUIRED = 117, + CANCELED = 118, + NO_SUCH_OPERATION = 119, + TOO_LATE = 120, + CANNOT_CANCEL = 121, + ASSERTION_FAILED = 122, + AUTHORIZATION_DENIED = 123, +}; + +#----------------------------------------------------------------------------- +public type Result = unit { + code: ASN1::ASN1Message(True) &convert=cast(cast($$.body.num_value)) + &default=ResultCode::Undef; + matchedDN: ASN1::ASN1Message(True) &convert=$$.body.str_value + &default=""; + diagnosticMessage: ASN1::ASN1Message(True) &convert=$$.body.str_value + &default=""; + + # TODO: if we want to parse referral URIs in result + # https://tools.ietf.org/html/rfc4511#section-4.1.10 +}; + +#----------------------------------------------------------------------------- +public type Messages = unit { + : MessageWrapper[]; +}; + +#----------------------------------------------------------------------------- +type SASLLayer = unit { + # For the time being (before we support parsing the SASL layer) this unit + # is used by MessageWrapper below to strip it (SASL) so that the parser + # can attempt to resume parsing afterward. It also sets the success flag + # if '\x30' is found, otherwise backtracks so that we can deal with encrypted + # SASL payloads without raising a parse error. + var success: bool = False; + : bytes &until=b"\x30" { + self.success = True; + } + + on %error { + self.backtrack(); + } +}; + +#----------------------------------------------------------------------------- +public type MessageWrapper = unit { + # A wrapper around 'Message'. First, we try to parse a Message unit. + # There are two possible outcomes: + # (1) Success -> We consumed all bytes and successfully parsed a Message unit + # (2) No success -> self.backtrack() is called in the Message unit, + # so effectively we didn't consume any bytes yet. + # The outcome can be determined by checking the `success` variable of the Message unit + + # This success variable is different, because this keeps track of the status for the MessageWrapper object + var success: bool = False; + var message: Message; + + # Here, we try to parse the message... + : Message &try { + + # ... and only if the Message unit successfully parsed, we can set + # the status of this MessageWrapper's success to 'True' + if ( $$.success == True ) { + self.success = True; + self.message = $$; + } + } + + # If we failed to parse the message, then we're going to scan the remaining bytes for the '\x30' + # start byte and try to parse a Message starting from that byte. This effectively + # strips the SASL layer if SASL Signing was enabled. Until now, I haven't found A + # better way to scan / determine the exact SASL header length yet, so we'll stick with this + # for the time being. If the entire LDAP packet was encrypted with SASL, then we skip parsing for + # now (in the long run we need to be parsing SASL/GSSAPI instead, in which case encrypted payloads + # are just another message type). + + # SASLLayer (see unit above) just consumes bytes &until=b"\x30" or backtracks if it isn't found + # and sets a success flag we can use later to decide if those bytes contain a parsable message. + var sasl_success: bool = False; + : SASLLayer &try if ( self.success == False ) { + if ( $$.success == True ) { + self.sasl_success = True; + } + } + var remainder: bytes; + + # SASLLayer consumes the delimiter ('\x30'), and because this is the first byte of a valid LDAP message + # we should re-add it to the remainder if the delimiter was found. If the delimiter was not found, we + # leave the remainder empty, but note that the bytes must be consumed either way to avoid stalling the + # parser and causing an infinite loop error. + : bytes &eod if ( self.success == False ) { + if ( self.sasl_success == True ) { + self.remainder = b"\x30" + $$; + } + } + + # Again, try to parse a Message unit. Be aware that in this will sometimes fail if the '\x30' byte is + # also present in the SASL header. + + # Also, we could try to do this recursively or try a few iterations, but for now I would suggest + # to try this extra parsing once to get the best cost/benefit tradeoff. + : Message &try &parse-from=self.remainder if ( self.success == False && self.sasl_success == True ) { + if ( $$.success == True ) { + self.success = True; + self.message = $$; + } + } + + # If we still didn't manage to parse a message (so the &try resulted in another backtrack()) then + # this is probably an encrypted LDAP message, so skip it + +} &convert=self.message; + +#----------------------------------------------------------------------------- +public type Message = unit { + var messageID: int64; + var opcode: ProtocolOpcode = ProtocolOpcode::Undef; + var applicationBytes: bytes; + var unsetResultDefault: Result; + var result: Result& = self.unsetResultDefault; + var obj: string = ""; + var arg: string = ""; + var success: bool = False; + + : ASN1::ASN1Message(True) { + if (($$.head.tag.type_ == ASN1::ASN1Type::Sequence) && + ($$.body?.seq) && + (|$$.body.seq.submessages| >= 2)) { + if ($$.body.seq.submessages[0].body?.num_value) { + self.messageID = $$.body.seq.submessages[0].body.num_value; + } + if ($$.body.seq.submessages[1]?.application_id) { + self.opcode = cast(cast($$.body.seq.submessages[1].application_id)); + self.applicationBytes = $$.body.seq.submessages[1].application_data; + } + } + } + + switch ( self.opcode ) { + ProtocolOpcode::BIND_REQUEST -> BIND_REQUEST: BindRequest(self); + ProtocolOpcode::BIND_RESPONSE -> BIND_RESPONSE: BindResponse(self); + ProtocolOpcode::UNBIND_REQUEST -> UNBIND_REQUEST: UnbindRequest(self); + ProtocolOpcode::SEARCH_REQUEST -> SEARCH_REQUEST: SearchRequest(self); + ProtocolOpcode::SEARCH_RESULT_ENTRY -> SEARCH_RESULT_ENTRY: SearchResultEntry(self); + ProtocolOpcode::SEARCH_RESULT_DONE -> SEARCH_RESULT_DONE: SearchResultDone(self); + ProtocolOpcode::MODIFY_REQUEST -> MODIFY_REQUEST: ModifyRequest(self); + ProtocolOpcode::MODIFY_RESPONSE -> MODIFY_RESPONSE: ModifyResponse(self); + ProtocolOpcode::ADD_RESPONSE -> ADD_RESPONSE: AddResponse(self); + ProtocolOpcode::DEL_REQUEST -> DEL_REQUEST: DelRequest(self); + ProtocolOpcode::DEL_RESPONSE -> DEL_RESPONSE: DelResponse(self); + ProtocolOpcode::MOD_DN_RESPONSE -> MOD_DN_RESPONSE: ModDNResponse(self); + ProtocolOpcode::COMPARE_RESPONSE -> COMPARE_RESPONSE: CompareResponse(self); + ProtocolOpcode::ABANDON_REQUEST -> ABANDON_REQUEST: AbandonRequest(self); + + # TODO: not yet implemented, redirect to NotImplemented because when we're + # 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::INTERMEDIATE_RESPONSE -> INTERMEDIATE_RESPONSE: NotImplemented(self); + ProtocolOpcode::MOD_DN_REQUEST -> MOD_DN_REQUEST: NotImplemented(self); + ProtocolOpcode::SEARCH_RESULT_REFERENCE -> SEARCH_RESULT_REFERENCE: NotImplemented(self); + } &parse-from=self.applicationBytes if ( self.opcode ); + + on %error { + self.backtrack(); + } + + on %done { + self.success = True; + } + +} &requires=((self?.messageID) && (self?.opcode) && (self.opcode != ProtocolOpcode::Undef)); + +#----------------------------------------------------------------------------- +# Bind Operation +# https://tools.ietf.org/html/rfc4511#section-4.2 + +public type BindAuthType = enum { + BIND_AUTH_SIMPLE = 0, + BIND_AUTH_SASL = 3, +}; + +type SaslCredentials = unit() { + mechanism: ASN1::ASN1Message(True) &convert=$$.body.str_value; + # TODO: if we want to parse the (optional) credentials string +}; + +# 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) { + # Do nothing +}; + +type BindRequest = unit(inout message: Message) { + version: ASN1::ASN1Message(True) &convert=$$.body.num_value; + name: ASN1::ASN1Message(True) &convert=$$.body.str_value { + message.obj = self.name; + } + var authType: BindAuthType = BindAuthType::Undef; + var authData: bytes = b""; + var simpleCreds: string = ""; + + : ASN1::ASN1Message(True) { + if ($$?.application_id) { + self.authType = cast(cast($$.application_id)); + self.authData = $$.application_data; + } + if ((self.authType == BindAuthType::BIND_AUTH_SIMPLE) && (|self.authData| > 0)) { + self.simpleCreds = self.authData.decode(); + if (|self.simpleCreds| > 0) { + message.arg = self.simpleCreds; + } + } + } + saslCreds: SaslCredentials() &parse-from=self.authData if ((self.authType == BindAuthType::BIND_AUTH_SASL) && + (|self.authData| > 0)) { + message.arg = self.saslCreds.mechanism; + } +} &requires=((self?.authType) && (self.authType != BindAuthType::Undef)); + +type BindResponse = unit(inout message: Message) { + : Result { + message.result = $$; + } + + # TODO: if we want to parse SASL credentials returned +}; + +#----------------------------------------------------------------------------- +# Unbind Operation +# https://tools.ietf.org/html/rfc4511#section-4.3 + +type UnbindRequest = unit(inout message: Message) { + # this page intentionally left blank +}; + +#----------------------------------------------------------------------------- +# Search Operation +# https://tools.ietf.org/html/rfc4511#section-4.5 + +public type SearchScope = enum { + SEARCH_BASE = 0, + SEARCH_SINGLE = 1, + SEARCH_TREE = 2, +}; + +public type SearchDerefAlias = enum { + DEREF_NEVER = 0, + DEREF_IN_SEARCHING = 1, + DEREF_FINDING_BASE = 2, + DEREF_ALWAYS = 3, +}; + +type FilterType = enum { + FILTER_AND = 0, + FILTER_OR = 1, + FILTER_NOT = 2, + FILTER_EQ = 3, + FILTER_SUBSTR = 4, + FILTER_GE = 5, + FILTER_LE = 6, + FILTER_PRESENT = 7, + FILTER_APPROX = 8, + FILTER_EXT = 9, + FILTER_INVALID = 254, +}; + +public type AttributeSelection = unit { + var attributes: vector; + + # TODO: parse AttributeSelection as per + # https://tools.ietf.org/html/rfc4511#section-4.5.1 + # and decide how deep that should be fleshed out. + : ASN1::ASN1Message(True) { + if (($$.head.tag.type_ == ASN1::ASN1Type::Sequence) && + ($$.body?.seq)) { + for (i in $$.body.seq.submessages) { + if (i.body?.str_value) { + self.attributes.push_back(i.body.str_value); + } + } + } + } +}; + +type AttributeValueAssertion = unit { + var desc: string = ""; + var val: string = ""; + + : ASN1::ASN1Message(True) { + if (($$.head.tag.type_ == ASN1::ASN1Type::Sequence) && + ($$.body?.seq) && + (|$$.body.seq.submessages| >= 2)) { + if ($$.body.seq.submessages[0].body?.str_value) { + self.desc = $$.body.seq.submessages[0].body.str_value; + } + if ($$.body.seq.submessages[1].body?.str_value) { + self.val = $$.body.seq.submessages[1].body.str_value; + } + } + } +}; + +# An AND or OR search filter can consist of many sub-searchfilters, so we try to parse these +type ParseNestedAndOr = unit { + searchfilters: SearchFilter[] &eod; +}; + +type ParseNestedNot = unit { + searchfilter: SearchFilter; +}; + +# Helper functions to properly format some custom data structures + +public function utf16_guid_to_hex_repr(bts: bytes) : string { + # Rather ugly workaround to pretty-print the CLDAP DomainGuid UTF16-LE encoded string + # in the same format as Wireshark (aabbccdd-eeff-gghh-iijj-kkllmmnnoopp) + + # We need to have exactly 16 bytes... + if ( |bts| != 16 ) { + # ... and otherwise just return an error code + return "GUID_FORMAT_FAILED"; + } + + local ret = ""; + for ( i in [[3, 2, 1, 0], [5, 4], [7, 6], [8, 9], [10, 11, 12, 13, 14, 15]] ) { + for ( j in i ) { + local bt: uint8 = *bts.at(j); + ret = ret + "%02x" % bt; + if ( j in [0, 4, 6, 9] ) { + ret = ret + "-"; + } + } + } + return ret; +} + +public function bytes_sid_to_hex_repr(bts: bytes) : string { + local ret = ""; + local cnt = 0; + + while ( cnt < |bts| ) { + local bt: uint8 = *bts.at(cnt); + ret = ret + "%02x" % bt; + + if ( cnt < |bts|-1 ) { + ret = ret + ":"; + } + cnt += 1; + } + return ret; +} + +public function bytes_sid_to_SID_repr(bts: bytes) : string { + # Example: SID -> S-1-5-21-1153942841-488947194-1912431946 + + # Needs to be exactly 24 bytes + if ( |bts| != 24 ) { + # ... and otherwise just return an error code + return "SID_FORMAT_FAILED"; + } + + local ret = "S-"; + local cnt = 0; + + # Mixed little and big endian, so turn everything to big endian first... + # Byte 1 seems to be skipped when parsing the SID + for ( i in [[0], [2, 3, 4, 5, 6, 7], [11, 10, 9, 8], [15, 14, 13, 12], [19, 18, 17, 16], [23, 22, 21, 20]] ) { + local dec_val_rep: bytes = b""; + for ( j in i ) { + local bt: uint8 = *bts.at(j); + dec_val_rep += bt; + cnt += 1; + } + + # ... so we can represent this integer value in big endian + ret = ret + "%u" % dec_val_rep.to_uint(spicy::ByteOrder::Big); + + # Only print the dash when we're not at the end + if ( cnt < 23 ) { + ret = ret + "-"; + } + } + return ret; +} + + +public function uint32_to_hex_repr(bts: bytes) : string { + # Needs to be exactly 4 bytes + if ( |bts| != 4 ) { + # ... and otherwise just return an error code + return "HEX_FORMAT_FAILED"; + } + + # Workaround to print the hex value of an uint32, prepended with '0x' + local ret = "0x"; + for ( i in [3, 2, 1, 0] ) { + local bt: uint8 = *bts.at(i); + ret = ret + "%02x" % bt; + } + return ret; +} + +# Helper to compute a string representation of a `SearchFilter`. +public function string_representation(search_filter: SearchFilter): string { + local repr: string; + + switch ( local fType = search_filter.filterType ) { + # The NOT, AND and OR filter types are trees and may hold many leaf nodes. So recursively get + # the stringPresentations for the leaf nodes and add them all in one final statement. + + case FilterType::FILTER_NOT: { + repr = "(!%s)" % search_filter.FILTER_NOT.searchfilter.stringRepresentation; + } + + case FilterType::FILTER_AND, FilterType::FILTER_OR: { + local nestedObj: ParseNestedAndOr; + local printChar = ""; + + if ( fType == FilterType::FILTER_AND ) { + printChar = "&"; + nestedObj = search_filter.FILTER_AND; + } else { + printChar = "|"; + nestedObj = search_filter.FILTER_OR; + } + + # Build the nested AND/OR statement in this loop. When we encounter the first element, + # we open the statement. At the second element, we close our first complete statement. For every + # following statement, we extend the AND/OR statement by wrapping it around the already completed + # statement. Although it is also valid to not do this wrapping, which is logically equivalent, e.g: + # + # (1) (2) + # (?(a=b)(c=d)(e=f)) vs (?(?(a=b)(c=d))(e=f)) + # + # the latter version is also shown by Wireshark. So although the parsed structure actually represents + # version (1) of the query, we now choose to print version (2). If this is not desirable, swap the code + # for the following: + # + # # Construct the nested structure, like (1) + # for ( SF in nestedObj.searchfilters ) { + # self.stringRepresentation = self.stringRepresentation + SF.stringRepresentation + # } + # # Close it with brackets and put the correct printChar for AND/OR in the statement + # self.stringRepresentation = "(%s" % printChar + self.stringRepresentation + ")"; + # + + local i = 0; + for ( searchFilter in nestedObj.searchfilters ) { + switch ( i ) { + case 0: { + repr = "(%s%s%s" % ( + printChar, + searchFilter.stringRepresentation, + # If we have exactly one element immediately close the statement since we are done. + |nestedObj.searchfilters| == 1 ? ")" : "" + ); + } + case 1: { + repr = repr + searchFilter.stringRepresentation + ")"; + } + default: { + repr = "(%s" % printChar + repr + searchFilter.stringRepresentation + ")"; + } + } + i += 1; + } + } + + # The following FilterTypes are leaf nodes and can thus be represented in a statement + + case FilterType::FILTER_EXT: { + # For extended search filters the meaning of the individual fields in + # `DecodedAttributeValue` is slightly different. + repr = "(%s:%s:=%s)" % (search_filter.FILTER_EXT.assertionValueDecoded, + search_filter.FILTER_EXT.attributeDesc.decode(), + search_filter.FILTER_EXT.matchValue); + } + case FilterType::FILTER_APPROX: { + repr = "(%s~=%s)" % (search_filter.FILTER_APPROX.attributeDesc.decode(), + search_filter.FILTER_APPROX.assertionValueDecoded); + } + case FilterType::FILTER_EQ: { + repr = "(%s=%s)" % (search_filter.FILTER_EQ.attributeDesc.decode(), + search_filter.FILTER_EQ.assertionValueDecoded); + } + case FilterType::FILTER_GE: { + repr = "(%s>=%s)" % (search_filter.FILTER_GE.attributeDesc.decode(), + search_filter.FILTER_GE.assertionValueDecoded); + } + case FilterType::FILTER_LE: { + repr = "(%s<=%s)" % (search_filter.FILTER_LE.attributeDesc.decode(), + search_filter.FILTER_LE.assertionValueDecoded); + } + case FilterType::FILTER_SUBSTR: { + repr = "(%s=*%s*)" % (search_filter.FILTER_SUBSTR.attributeDesc.decode(), + search_filter.FILTER_SUBSTR.assertionValueDecoded); + } + case FilterType::FILTER_PRESENT: { + repr = "(%s=*)" % search_filter.FILTER_PRESENT; + } + } + + return repr; +} + +# Represents an (extended) key-value pair present in SearchFilters +type DecodedAttributeValue = unit(fType: FilterType) { + var assertionValueDecoded: string = ""; + + : uint8; + attributeDesc_len: uint8; + attributeDesc: bytes &size=self.attributeDesc_len; + + # For some reason, two intermediate uint8 values are present in the FILTER_SUBSTR type. + : uint8 if ( fType == FilterType::FILTER_SUBSTR ); + : uint8 if ( fType == FilterType::FILTER_SUBSTR ); + + : uint8; + assertionValue_len: uint8; + assertionValue: bytes &size=self.assertionValue_len; + + # Only for the FILTER_EXT type, parse extra fields + : uint8 if ( fType == FilterType::FILTER_EXT ); + matchValue_len: uint8 if( fType == FilterType::FILTER_EXT ); + matchValue: bytes &size=self.matchValue_len if ( fType == FilterType::FILTER_EXT ); + + on %done { + switch ( self.attributeDesc ) { + # Special parsing required for some CLDAP attributes, + # see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/895a7744-aff3-4f64-bcfa-f8c05915d2e9 + + case b"DomainGuid": { + self.assertionValueDecoded = utf16_guid_to_hex_repr(self.assertionValue); + } + + case b"objectSid", b"AAC": { + self.assertionValueDecoded = bytes_sid_to_hex_repr(self.assertionValue); + } + + case b"DomainSid": { + self.assertionValueDecoded = bytes_sid_to_SID_repr(self.assertionValue); + } + + case b"NtVer": { + self.assertionValueDecoded = uint32_to_hex_repr(self.assertionValue); + } + + # By default, decode with UTF-8 + default: { + self.assertionValueDecoded = self.assertionValue.decode(); + } + } + } +}; + +type SearchFilter = unit { + var filterType: FilterType = FilterType::Undef; + var filterBytes: bytes = b""; + var filterLen: uint64 = 0; + var stringRepresentation: string = ""; + + : ASN1::ASN1Message(True) { + if ($$?.application_id) { + self.filterType = cast(cast($$.application_id)); + self.filterBytes = $$.application_data; + self.filterLen = $$.head.len.len; + } else { + self.filterType = FilterType::FILTER_INVALID; + } + } + + switch ( self.filterType ) { + + # FilterTypes that hold one or more SearchFilters inside them + + FilterType::FILTER_AND -> FILTER_AND: ParseNestedAndOr() + &parse-from=self.filterBytes; + FilterType::FILTER_OR -> FILTER_OR: ParseNestedAndOr() + &parse-from=self.filterBytes; + FilterType::FILTER_NOT -> FILTER_NOT: ParseNestedNot() + &parse-from=self.filterBytes; + + # FilterTypes that we can actually convert to a string + + FilterType::FILTER_EQ -> FILTER_EQ: DecodedAttributeValue(FilterType::FILTER_EQ) + &parse-from=self.filterBytes; + FilterType::FILTER_SUBSTR -> FILTER_SUBSTR: DecodedAttributeValue(FilterType::FILTER_SUBSTR) + &parse-from=self.filterBytes; + FilterType::FILTER_GE -> FILTER_GE: DecodedAttributeValue(FilterType::FILTER_GE) + &parse-from=self.filterBytes; + FilterType::FILTER_LE -> FILTER_LE: DecodedAttributeValue(FilterType::FILTER_LE) + &parse-from=self.filterBytes; + FilterType::FILTER_APPROX -> FILTER_APPROX: DecodedAttributeValue(FilterType::FILTER_APPROX) + &parse-from=self.filterBytes; + FilterType::FILTER_EXT -> FILTER_EXT: DecodedAttributeValue(FilterType::FILTER_EXT) + &parse-from=self.filterBytes; + FilterType::FILTER_PRESENT -> FILTER_PRESENT: ASN1::ASN1OctetString(self.filterLen, False) + &convert=$$.value.decode(hilti::Charset::ASCII) + &parse-from=self.filterBytes; + }; + + # So when you're done with recursively parsing the filters, we can now leverage the tree structure to + # recursively get the stringRepresentations for those leafs, which are SearchFilters + + on %done { + self.stringRepresentation = string_representation(self); + } + + on %error { + self.stringRepresentation = "FILTER_PARSING_ERROR"; + } + +}; + +public type SearchRequest = unit(inout message: Message) { + baseObject: ASN1::ASN1Message(True) &convert=$$.body.str_value { + message.obj = self.baseObject; + } + scope: ASN1::ASN1Message(True) &convert=cast(cast($$.body.num_value)) + &default=SearchScope::Undef { + message.arg = "%s" % self.scope; + } + deref: ASN1::ASN1Message(True) &convert=cast(cast($$.body.num_value)) + &default=SearchDerefAlias::Undef; + sizeLimit: ASN1::ASN1Message(True) &convert=$$.body.num_value &default=0; + timeLimit: ASN1::ASN1Message(True) &convert=$$.body.num_value &default=0; + typesOnly: ASN1::ASN1Message(True) &convert=$$.body.bool_value &default=False; + filter: SearchFilter &convert=$$.stringRepresentation; + attributes: AttributeSelection &convert=$$.attributes; +}; + +type SearchResultEntry = unit(inout message: Message) { + objectName: ASN1::ASN1Message(True) &convert=$$.body.str_value { + message.obj = self.objectName; + } + # TODO: if we want to descend down into PartialAttributeList + attributes: ASN1::ASN1Message(True); +}; + +type SearchResultDone = unit(inout message: Message) { + : Result { + message.result = $$; + } +}; + +# TODO: implement SearchResultReference +# type SearchResultReference = unit(inout message: Message) { +# +# }; + +#----------------------------------------------------------------------------- +# Modify Operation +# https://tools.ietf.org/html/rfc4511#section-4.6 + +type ModifyRequest = unit(inout message: Message) { + objectName: ASN1::ASN1Message(True) &convert=$$.body.str_value { + message.obj = self.objectName; + } + + # TODO: parse changes +}; + +type ModifyResponse = unit(inout message: Message) { + : Result { + message.result = $$; + } +}; + +#----------------------------------------------------------------------------- +# Add Operation +# https://tools.ietf.org/html/rfc4511#section-4.7 + +# TODO: implement AddRequest +# type AddRequest = unit(inout message: Message) { +# +# +# }; + +type AddResponse = unit(inout message: Message) { + : Result { + message.result = $$; + } +}; + +#----------------------------------------------------------------------------- +# Delete Operation +# https://tools.ietf.org/html/rfc4511#section-4.8 + +type DelRequest = unit(inout message: Message) { + objectName: ASN1::ASN1Message(True) &convert=$$.body.str_value { + message.obj = self.objectName; + } +}; + +type DelResponse = unit(inout message: Message) { + : Result { + message.result = $$; + } +}; + +#----------------------------------------------------------------------------- +# Modify DN Operation +# https://tools.ietf.org/html/rfc4511#section-4.8 + +# TODO: implement ModDNRequest +# type ModDNRequest = unit(inout message: Message) { +# +# }; + +type ModDNResponse = unit(inout message: Message) { + : Result { + message.result = $$; + } +}; + +#----------------------------------------------------------------------------- +# Compare Operation +# https://tools.ietf.org/html/rfc4511#section-4.10 + +# TODO: implement CompareRequest +# type CompareRequest = unit(inout message: Message) { +# +# }; + +type CompareResponse = unit(inout message: Message) { + : Result { + message.result = $$; + } +}; + +#----------------------------------------------------------------------------- +# Abandon Operation +# https://tools.ietf.org/html/rfc4511#section-4.11 + +type AbandonRequest = unit(inout message: Message) { + messageID: ASN1::ASN1Message(True) &convert=$$.body.num_value { + message.obj = "%d" % (self.messageID); + } +}; + +#----------------------------------------------------------------------------- +# Extended Operation +# https://tools.ietf.org/html/rfc4511#section-4.12 + +# TODO: implement ExtendedRequest +# type ExtendedRequest = unit(inout message: Message) { +# +# }; + +# TODO: implement ExtendedResponse +# type ExtendedResponse = unit(inout message: Message) { +# +# }; + +#----------------------------------------------------------------------------- +# IntermediateResponse Message +# https://tools.ietf.org/html/rfc4511#section-4.13 + +# TODO: implement IntermediateResponse +# type IntermediateResponse = unit(inout message: Message) { +# +# }; + +on LDAP::MessageWrapper::%done { + spicy::accept_input(); +} diff --git a/testing/btest/Baseline/core.print-bpf-filters/output2 b/testing/btest/Baseline/core.print-bpf-filters/output2 index 939abe6f39..dcd56a5e77 100644 --- a/testing/btest/Baseline/core.print-bpf-filters/output2 +++ b/testing/btest/Baseline/core.print-bpf-filters/output2 @@ -18,9 +18,11 @@ 1 25 1 2811 1 3128 +1 3268 1 3306 2 3389 1 3544 +2 389 1 4011 2 443 1 445 @@ -60,8 +62,8 @@ 1 992 1 993 1 995 -67 and -66 or -67 port -44 tcp -23 udp +70 and +69 or +70 port +46 tcp +24 udp diff --git a/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log b/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log index 7ad3d68c64..985e870bf5 100644 --- a/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log +++ b/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log @@ -406,6 +406,9 @@ scripts/base/init-default.zeek scripts/base/protocols/krb/main.zeek scripts/base/protocols/krb/consts.zeek scripts/base/protocols/krb/files.zeek + scripts/base/protocols/ldap/__load__.zeek + scripts/base/protocols/ldap/consts.zeek + scripts/base/protocols/ldap/main.zeek scripts/base/protocols/modbus/__load__.zeek scripts/base/protocols/modbus/consts.zeek scripts/base/protocols/modbus/main.zeek diff --git a/testing/btest/Baseline/coverage.find-bro-logs/out b/testing/btest/Baseline/coverage.find-bro-logs/out index f410d976ab..bd3b78858e 100644 --- a/testing/btest/Baseline/coverage.find-bro-logs/out +++ b/testing/btest/Baseline/coverage.find-bro-logs/out @@ -20,6 +20,8 @@ known_certs known_hosts known_modbus known_services +ldap +ldap_search loaded_scripts modbus modbus_register_change diff --git a/testing/btest/Baseline/coverage.record-fields/out.default b/testing/btest/Baseline/coverage.record-fields/out.default index 25d9e950bf..b53dbd29b1 100644 --- a/testing/btest/Baseline/coverage.record-fields/out.default +++ b/testing/btest/Baseline/coverage.record-fields/out.default @@ -360,6 +360,39 @@ connection { * ts: time, log=T, optional=F * uid: string, log=T, optional=F } + * ldap: record LDAP::State, log=F, optional=T + LDAP::State { + * messages: table[int] of record LDAP::MessageInfo, log=F, optional=T + LDAP::MessageInfo { + * arguments: vector of string, log=T, optional=T + * diagnostic_messages: vector of string, log=T, optional=T + * id: record conn_id, log=T, optional=F + conn_id { ... } + * message_id: int, log=T, optional=T + * objects: vector of string, log=T, optional=T + * opcodes: set[string], log=T, optional=T + * results: set[string], log=T, optional=T + * ts: time, log=T, optional=F + * uid: string, log=T, optional=F + * version: int, log=T, optional=T + } + * searches: table[int] of record LDAP::SearchInfo, log=F, optional=T + LDAP::SearchInfo { + * attributes: vector of string, log=T, optional=T + * base_objects: vector of string, log=T, optional=T + * derefs: set[string], log=T, optional=T + * diagnostic_messages: vector of string, log=T, optional=T + * filter: string, log=T, optional=T + * id: record conn_id, log=T, optional=F + conn_id { ... } + * message_id: int, log=T, optional=T + * result_count: count, log=T, optional=T + * results: set[string], log=T, optional=T + * scopes: set[string], log=T, optional=T + * ts: time, log=T, optional=F + * uid: string, log=T, optional=F + } + } * modbus: record Modbus::Info, log=F, optional=T Modbus::Info { * exception: string, log=T, optional=T diff --git a/testing/btest/Baseline/scripts.base.files.x509.files/files.log b/testing/btest/Baseline/scripts.base.files.x509.files/files.log index e64dfc52c0..ce19924fa1 100644 --- a/testing/btest/Baseline/scripts.base.files.x509.files/files.log +++ b/testing/btest/Baseline/scripts.base.files.x509.files/files.log @@ -7,10 +7,10 @@ #open XXXX-XX-XX-XX-XX-XX #fields ts fuid uid id.orig_h id.orig_p id.resp_h id.resp_p source depth analyzers mime_type filename duration local_orig is_orig seen_bytes total_bytes missing_bytes overflow_bytes timedout parent_fuid md5 sha1 sha256 #types time string string addr port addr port string count set[string] string string interval bool bool count count count count bool string string string string -XXXXXXXXXX.XXXXXX FgN3AE3of2TRIqaeQe CHhAvVGS1DHFjwGM9 192.168.4.149 60623 74.125.239.129 443 SSL 0 SHA256,X509,SHA1,MD5 application/x-x509-user-cert - 0.000000 F F 1859 - 0 0 F - 7af07aca6d5c6e8e87fe4bb34786edc0 548b9e03bc183d1cd39f93a37985cb3950f8f06f 6bacfa4536150ed996f2b0c05ab6e345a257225f449aeb9d2018ccd88f4ede43 -XXXXXXXXXX.XXXXXX Fv2Agc4z5boBOacQi6 CHhAvVGS1DHFjwGM9 192.168.4.149 60623 74.125.239.129 443 SSL 0 SHA256,X509,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 1032 - 0 0 F - 9e4ac96474245129d9766700412a1f89 d83c1a7f4d0446bb2081b81a1670f8183451ca24 a047a37fa2d2e118a4f5095fe074d6cfe0e352425a7632bf8659c03919a6c81d -XXXXXXXXXX.XXXXXX Ftmyeg2qgI2V38Dt3g CHhAvVGS1DHFjwGM9 192.168.4.149 60623 74.125.239.129 443 SSL 0 SHA256,X509,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 897 - 0 0 F - 2e7db2a31d0e3da4b25f49b9542a2e1a 7359755c6df9a0abc3060bce369564c8ec4542a3 3c35cc963eb004451323d3275d05b353235053490d9cd83729a2faf5e7ca1cc0 -XXXXXXXXXX.XXXXXX FUFNf84cduA0IJCp07 ClEkJM2Vm5giqnMf4h 192.168.4.149 60624 74.125.239.129 443 SSL 0 SHA256,X509,SHA1,MD5 application/x-x509-user-cert - 0.000000 F F 1859 - 0 0 F - 7af07aca6d5c6e8e87fe4bb34786edc0 548b9e03bc183d1cd39f93a37985cb3950f8f06f 6bacfa4536150ed996f2b0c05ab6e345a257225f449aeb9d2018ccd88f4ede43 -XXXXXXXXXX.XXXXXX F1H4bd2OKGbLPEdHm4 ClEkJM2Vm5giqnMf4h 192.168.4.149 60624 74.125.239.129 443 SSL 0 SHA256,X509,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 1032 - 0 0 F - 9e4ac96474245129d9766700412a1f89 d83c1a7f4d0446bb2081b81a1670f8183451ca24 a047a37fa2d2e118a4f5095fe074d6cfe0e352425a7632bf8659c03919a6c81d -XXXXXXXXXX.XXXXXX Fgsbci2jxFXYMOHOhi ClEkJM2Vm5giqnMf4h 192.168.4.149 60624 74.125.239.129 443 SSL 0 SHA256,X509,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 897 - 0 0 F - 2e7db2a31d0e3da4b25f49b9542a2e1a 7359755c6df9a0abc3060bce369564c8ec4542a3 3c35cc963eb004451323d3275d05b353235053490d9cd83729a2faf5e7ca1cc0 +XXXXXXXXXX.XXXXXX FgN3AE3of2TRIqaeQe CHhAvVGS1DHFjwGM9 192.168.4.149 60623 74.125.239.129 443 SSL 0 X509,SHA256,SHA1,MD5 application/x-x509-user-cert - 0.000000 F F 1859 - 0 0 F - 7af07aca6d5c6e8e87fe4bb34786edc0 548b9e03bc183d1cd39f93a37985cb3950f8f06f 6bacfa4536150ed996f2b0c05ab6e345a257225f449aeb9d2018ccd88f4ede43 +XXXXXXXXXX.XXXXXX Fv2Agc4z5boBOacQi6 CHhAvVGS1DHFjwGM9 192.168.4.149 60623 74.125.239.129 443 SSL 0 X509,SHA256,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 1032 - 0 0 F - 9e4ac96474245129d9766700412a1f89 d83c1a7f4d0446bb2081b81a1670f8183451ca24 a047a37fa2d2e118a4f5095fe074d6cfe0e352425a7632bf8659c03919a6c81d +XXXXXXXXXX.XXXXXX Ftmyeg2qgI2V38Dt3g CHhAvVGS1DHFjwGM9 192.168.4.149 60623 74.125.239.129 443 SSL 0 X509,SHA256,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 897 - 0 0 F - 2e7db2a31d0e3da4b25f49b9542a2e1a 7359755c6df9a0abc3060bce369564c8ec4542a3 3c35cc963eb004451323d3275d05b353235053490d9cd83729a2faf5e7ca1cc0 +XXXXXXXXXX.XXXXXX FUFNf84cduA0IJCp07 ClEkJM2Vm5giqnMf4h 192.168.4.149 60624 74.125.239.129 443 SSL 0 X509,SHA256,SHA1,MD5 application/x-x509-user-cert - 0.000000 F F 1859 - 0 0 F - 7af07aca6d5c6e8e87fe4bb34786edc0 548b9e03bc183d1cd39f93a37985cb3950f8f06f 6bacfa4536150ed996f2b0c05ab6e345a257225f449aeb9d2018ccd88f4ede43 +XXXXXXXXXX.XXXXXX F1H4bd2OKGbLPEdHm4 ClEkJM2Vm5giqnMf4h 192.168.4.149 60624 74.125.239.129 443 SSL 0 X509,SHA256,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 1032 - 0 0 F - 9e4ac96474245129d9766700412a1f89 d83c1a7f4d0446bb2081b81a1670f8183451ca24 a047a37fa2d2e118a4f5095fe074d6cfe0e352425a7632bf8659c03919a6c81d +XXXXXXXXXX.XXXXXX Fgsbci2jxFXYMOHOhi ClEkJM2Vm5giqnMf4h 192.168.4.149 60624 74.125.239.129 443 SSL 0 X509,SHA256,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 897 - 0 0 F - 2e7db2a31d0e3da4b25f49b9542a2e1a 7359755c6df9a0abc3060bce369564c8ec4542a3 3c35cc963eb004451323d3275d05b353235053490d9cd83729a2faf5e7ca1cc0 #close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.attributes/conn.log b/testing/btest/Baseline/scripts.base.protocols.ldap.attributes/conn.log new file mode 100644 index 0000000000..a69f1ec56a --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.attributes/conn.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path conn +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state 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 count string count count count count set[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 tcp ldap_tcp 181.520479 258 188 RSTO 0 ShADdR 8 590 4 360 - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.attributes/ldap.log b/testing/btest/Baseline/scripts.base.protocols.ldap.attributes/ldap.log new file mode 100644 index 0000000000..281b0cbcd1 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.attributes/ldap.log @@ -0,0 +1,12 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ldap +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcodes results diagnostic_messages objects arguments +#types time string addr port addr port int int set[string] set[string] vector[string] vector[string] vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 1 3 bind simple success - xxxxxxxxxxx@xx.xxx.xxxxx.net REDACTED +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 3 3 bind simple success - CN=xxxxxxxx\x2cOU=Users\x2cOU=Accounts\x2cDC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net REDACTED +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.attributes/ldap_search.log b/testing/btest/Baseline/scripts.base.protocols.ldap.attributes/ldap_search.log new file mode 100644 index 0000000000..018acb7c16 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.attributes/ldap_search.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ldap_search +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scopes derefs base_objects result_count results diagnostic_messages filter attributes +#types time string addr port addr port int set[string] set[string] vector[string] count set[string] vector[string] string vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 2 tree always DC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net 1 success - (&(objectclass=*)(sAMAccountName=xxxxxxxx)) sAMAccountName +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.basic/conn.log b/testing/btest/Baseline/scripts.base.protocols.ldap.basic/conn.log new file mode 100644 index 0000000000..a69f1ec56a --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.basic/conn.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path conn +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state 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 count string count count count count set[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 tcp ldap_tcp 181.520479 258 188 RSTO 0 ShADdR 8 590 4 360 - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.basic/ldap.log b/testing/btest/Baseline/scripts.base.protocols.ldap.basic/ldap.log new file mode 100644 index 0000000000..281b0cbcd1 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.basic/ldap.log @@ -0,0 +1,12 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ldap +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcodes results diagnostic_messages objects arguments +#types time string addr port addr port int int set[string] set[string] vector[string] vector[string] vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 1 3 bind simple success - xxxxxxxxxxx@xx.xxx.xxxxx.net REDACTED +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 3 3 bind simple success - CN=xxxxxxxx\x2cOU=Users\x2cOU=Accounts\x2cDC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net REDACTED +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.basic/ldap_search.log b/testing/btest/Baseline/scripts.base.protocols.ldap.basic/ldap_search.log new file mode 100644 index 0000000000..33922f0dea --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.basic/ldap_search.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ldap_search +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scopes derefs base_objects result_count results diagnostic_messages filter attributes +#types time string addr port addr port int set[string] set[string] vector[string] count set[string] vector[string] string vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 2 tree always DC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net 1 success - (&(objectclass=*)(sAMAccountName=xxxxxxxx)) - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.basic/output b/testing/btest/Baseline/scripts.base.protocols.ldap.basic/output new file mode 100644 index 0000000000..49d861c74c --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.basic/output @@ -0,0 +1 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.diff_port/conn.log b/testing/btest/Baseline/scripts.base.protocols.ldap.diff_port/conn.log new file mode 100644 index 0000000000..f60bcc1b32 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.diff_port/conn.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path conn +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state 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 count string count count count count set[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 32681 tcp ldap_tcp 181.520479 258 188 RSTO 0 ShADdR 8 590 4 360 - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.diff_port/ldap.log b/testing/btest/Baseline/scripts.base.protocols.ldap.diff_port/ldap.log new file mode 100644 index 0000000000..4661b1bf3f --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.diff_port/ldap.log @@ -0,0 +1,12 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ldap +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcodes results diagnostic_messages objects arguments +#types time string addr port addr port int int set[string] set[string] vector[string] vector[string] vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 32681 1 3 bind simple success - xxxxxxxxxxx@xx.xxx.xxxxx.net REDACTED +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 32681 3 3 bind simple success - CN=xxxxxxxx\x2cOU=Users\x2cOU=Accounts\x2cDC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net REDACTED +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.diff_port/ldap_search.log b/testing/btest/Baseline/scripts.base.protocols.ldap.diff_port/ldap_search.log new file mode 100644 index 0000000000..4bb7c32ec7 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.diff_port/ldap_search.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ldap_search +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scopes derefs base_objects result_count results diagnostic_messages filter attributes +#types time string addr port addr port int set[string] set[string] vector[string] count set[string] vector[string] string vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 32681 2 tree always DC=xx\x2cDC=xxx\x2cDC=xxxxx\x2cDC=net 1 success - (&(objectclass=*)(sAMAccountName=xxxxxxxx)) - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.log_policy/conn.log b/testing/btest/Baseline/scripts.base.protocols.ldap.log_policy/conn.log new file mode 100644 index 0000000000..a69f1ec56a --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.log_policy/conn.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path conn +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state 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 count string count count count count set[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.0.0.1 25936 10.0.0.2 3268 tcp ldap_tcp 181.520479 258 188 RSTO 0 ShADdR 8 590 4 360 - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.log_policy/output b/testing/btest/Baseline/scripts.base.protocols.ldap.log_policy/output new file mode 100644 index 0000000000..49d861c74c --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.log_policy/output @@ -0,0 +1 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.sasl-encrypted/conn.log b/testing/btest/Baseline/scripts.base.protocols.ldap.sasl-encrypted/conn.log new file mode 100644 index 0000000000..eaaa60672a --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.sasl-encrypted/conn.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path conn +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state 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 count string count count count count set[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.31.1.104 3116 172.31.1.101 389 tcp ldap_tcp 0.813275 1814 2391 S1 0 ShADd 6 2062 4 2559 - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.sasl-encrypted/ldap.log b/testing/btest/Baseline/scripts.base.protocols.ldap.sasl-encrypted/ldap.log new file mode 100644 index 0000000000..e38f28a9f7 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.sasl-encrypted/ldap.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ldap +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id version opcodes results diagnostic_messages objects arguments +#types time string addr port addr port int int set[string] set[string] vector[string] vector[string] vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.31.1.104 3116 172.31.1.101 389 215 3 bind SASL success - - GSS-SPNEGO +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.sasl-encrypted/ldap_search.log b/testing/btest/Baseline/scripts.base.protocols.ldap.sasl-encrypted/ldap_search.log new file mode 100644 index 0000000000..f2fd183303 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.sasl-encrypted/ldap_search.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ldap_search +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p message_id scopes derefs base_objects result_count results diagnostic_messages filter attributes +#types time string addr port addr port int set[string] set[string] vector[string] count set[string] vector[string] string vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.31.1.104 3116 172.31.1.101 389 213 base never - 1 success - (objectclass=*) - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.ldap.search_filter_extended/ldap_search.log b/testing/btest/Baseline/scripts.base.protocols.ldap.search_filter_extended/ldap_search.log new file mode 100644 index 0000000000..e208fdd3c8 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ldap.search_filter_extended/ldap_search.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path ldap_search +#open XXXX-XX-XX-XX-XX-XX +#fields uid filter base_objects +#types string string vector[string] +CHhAvVGS1DHFjwGM9 (departmentNumber:2.16.840.1.113730.3.3.2.46.1:=>=N4709) DC=matrix\x2cDC=local +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Traces/README b/testing/btest/Traces/README index f7ea25b807..bc5a304436 100644 --- a/testing/btest/Traces/README +++ b/testing/btest/Traces/README @@ -6,4 +6,15 @@ depend on them for tests. Trace Index/Sources: -- modbus/modbus-eit.trace: Sourced from https://www.netresec.com/?page=PCAP4SICS, credit to https://cs3sthlm.se/. The packets in this trace were pulled from the 4SICS-GeekLounge-151021.pcap file. \ No newline at end of file +- modbus/modbus-eit.trace: Sourced from https://www.netresec.com/?page=PCAP4SICS, credit to https://cs3sthlm.se/. The packets in this trace were pulled from the 4SICS-GeekLounge-151021.pcap file. + +- [ldap/simpleauth.pcap](https://github.com/arkime/arkime/blob/main/tests/pcap/ldap-simpleauth.pcap) +- ldap/simpleauth-diff-port.pcap: made with + `tcprewrite -r 3268:32681 -i simpleauth.pcap -o simpleauth-diff-port.pcap` +- ldap/krb5-sign-seal-01.pcap: trace is derived from + + - the LDAP flow selected (filtered out the Kerberos packets) + - truncated to 10 packets (where packet 10 contains the SASL encrypted LDAP message) + - one `\x30` byte in the ciphertext changed to `\x00` +- ldap/issue-32.pcapng: Provided by GH user martinvanhensbergen, + diff --git a/testing/btest/Traces/ldap/issue-32.pcapng b/testing/btest/Traces/ldap/issue-32.pcapng new file mode 100644 index 0000000000..8ed316dd51 Binary files /dev/null and b/testing/btest/Traces/ldap/issue-32.pcapng differ diff --git a/testing/btest/Traces/ldap/krb5-sign-seal-01.pcap b/testing/btest/Traces/ldap/krb5-sign-seal-01.pcap new file mode 100644 index 0000000000..a5591cb571 Binary files /dev/null and b/testing/btest/Traces/ldap/krb5-sign-seal-01.pcap differ diff --git a/testing/btest/Traces/ldap/simpleauth-diff-port.pcap b/testing/btest/Traces/ldap/simpleauth-diff-port.pcap new file mode 100644 index 0000000000..649078aa95 Binary files /dev/null and b/testing/btest/Traces/ldap/simpleauth-diff-port.pcap differ diff --git a/testing/btest/Traces/ldap/simpleauth.pcap b/testing/btest/Traces/ldap/simpleauth.pcap new file mode 100644 index 0000000000..1cf904a006 Binary files /dev/null and b/testing/btest/Traces/ldap/simpleauth.pcap differ diff --git a/testing/btest/core/print-bpf-filters.zeek b/testing/btest/core/print-bpf-filters.zeek index fd86ce4f04..e755c4347f 100644 --- a/testing/btest/core/print-bpf-filters.zeek +++ b/testing/btest/core/print-bpf-filters.zeek @@ -1,3 +1,5 @@ +# @TEST-REQUIRES: have-spicy +# # @TEST-EXEC: zeek -r $TRACES/empty.trace >output # @TEST-EXEC: cat packet_filter.log >>output # @TEST-EXEC: zeek -r $TRACES/empty.trace -f "port 42" >>output @@ -6,10 +8,9 @@ # @TEST-EXEC: cat packet_filter.log >>output # @TEST-EXEC: btest-diff output # @TEST-EXEC: btest-diff conn.log -# +# # The order in the output of enable_auto_protocol_capture_filters isn't # stable, for reasons not clear. We canonify it first. # @TEST-EXEC: zeek -r $TRACES/empty.trace PacketFilter::enable_auto_protocol_capture_filters=T # @TEST-EXEC: cat packet_filter.log | zeek-cut filter | sed 's#[()]##g' | tr ' ' '\n' | sort | uniq -c | awk '{print $1, $2}' >output2 # @TEST-EXEC: btest-diff output2 - diff --git a/testing/btest/coverage/find-bro-logs.test b/testing/btest/coverage/find-bro-logs.test index 01e822deef..1dd68d1492 100644 --- a/testing/btest/coverage/find-bro-logs.test +++ b/testing/btest/coverage/find-bro-logs.test @@ -4,6 +4,7 @@ # # If this test fails, then the "Log Files" documentation page should be updated. +# @TEST-REQUIRES: ${SCRIPTS}/have-spicy # @TEST-REQUIRES: which python3 # @TEST-EXEC: bash %INPUT # @TEST-EXEC: btest-diff out diff --git a/testing/btest/scripts/base/files/x509/files.test b/testing/btest/scripts/base/files/x509/files.test index 494bdf5c2a..b51bf39d2a 100644 --- a/testing/btest/scripts/base/files/x509/files.test +++ b/testing/btest/scripts/base/files/x509/files.test @@ -1,5 +1,6 @@ # Test that checks that files.log is generated if the respective option is set. +# @TEST-REQUIRES: ${SCRIPTS}/have-spicy # @TEST-EXEC: zeek -b -r $TRACES/tls/google-duplicate.trace %INPUT # @TEST-EXEC: btest-diff files.log diff --git a/testing/btest/scripts/base/protocols/ldap/attributes.zeek b/testing/btest/scripts/base/protocols/ldap/attributes.zeek new file mode 100644 index 0000000000..cc31bce939 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ldap/attributes.zeek @@ -0,0 +1,12 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +# @TEST-REQUIRES: have-spicy +# @TEST-EXEC: zeek -C -r ${TRACES}/ldap/simpleauth.pcap %INPUT +# @TEST-EXEC: cat conn.log | zeek-cut -Cn local_orig local_resp > conn.log2 && mv conn.log2 conn.log +# @TEST-EXEC: btest-diff conn.log +# @TEST-EXEC: btest-diff ldap.log +# @TEST-EXEC: btest-diff ldap_search.log +# +# @TEST-DOC: Test LDAP search attributes with small trace. + +redef LDAP::default_log_search_attributes = T; diff --git a/testing/btest/scripts/base/protocols/ldap/availability.zeek b/testing/btest/scripts/base/protocols/ldap/availability.zeek new file mode 100644 index 0000000000..a24ff3f799 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ldap/availability.zeek @@ -0,0 +1,6 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +# @TEST-REQUIRES: have-spicy +# @TEST-EXEC: zeek -NN | grep -q ANALYZER_LDAP_TCP +# +# @TEST-DOC: Check that LDAP (TCP) is analyzer is available. diff --git a/testing/btest/scripts/base/protocols/ldap/basic.zeek b/testing/btest/scripts/base/protocols/ldap/basic.zeek new file mode 100644 index 0000000000..62bd3d817d --- /dev/null +++ b/testing/btest/scripts/base/protocols/ldap/basic.zeek @@ -0,0 +1,11 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +# @TEST-REQUIRES: have-spicy +# @TEST-EXEC: zeek -C -r ${TRACES}/ldap/simpleauth.pcap %INPUT >output 2>&1 +# @TEST-EXEC: btest-diff output +# @TEST-EXEC: cat conn.log | zeek-cut -Cn local_orig local_resp > conn.log2 && mv conn.log2 conn.log +# @TEST-EXEC: btest-diff conn.log +# @TEST-EXEC: btest-diff ldap.log +# @TEST-EXEC: btest-diff ldap_search.log +# +# @TEST-DOC: Test LDAP analyzer with small trace. diff --git a/testing/btest/scripts/base/protocols/ldap/diff_port.zeek b/testing/btest/scripts/base/protocols/ldap/diff_port.zeek new file mode 100644 index 0000000000..2f76163f1b --- /dev/null +++ b/testing/btest/scripts/base/protocols/ldap/diff_port.zeek @@ -0,0 +1,10 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +# @TEST-REQUIRES: have-spicy +# @TEST-EXEC: zeek -C -r ${TRACES}/ldap/simpleauth-diff-port.pcap %INPUT +# @TEST-EXEC: cat conn.log | zeek-cut -Cn local_orig local_resp > conn.log2 && mv conn.log2 conn.log +# @TEST-EXEC: btest-diff conn.log +# @TEST-EXEC: btest-diff ldap.log +# @TEST-EXEC: btest-diff ldap_search.log +# +# @TEST-DOC: Test LDAP analyzer with small trace. diff --git a/testing/btest/scripts/base/protocols/ldap/functions.spicy b/testing/btest/scripts/base/protocols/ldap/functions.spicy new file mode 100644 index 0000000000..468e529f57 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ldap/functions.spicy @@ -0,0 +1,134 @@ +# This test can only run if we have the LDAP grammar available. +# @TEST-REQUIRES: have-spicy && [ -n ${DIST} ] +# +# @TEST-EXEC: spicyc -j -d -L ${DIST}/src/analyzer/protocol/ldap %INPUT +# +# @TEST-DOC: Validates helper functions in LDAP module. + +module test; + +import LDAP; + +# ---------------------------------------------------------------------------------- +# function utf16_guid_to_hex_repr() +# - requires exactly 16 bytes + +# Not enough bytes (15) +assert LDAP::utf16_guid_to_hex_repr(b"1234567890ABCDE") == "GUID_FORMAT_FAILED"; + +# Too much bytes (17) +assert LDAP::utf16_guid_to_hex_repr(b"1234567890ABCDEFG") == "GUID_FORMAT_FAILED"; + +# Empty +assert LDAP::utf16_guid_to_hex_repr(b"") == "GUID_FORMAT_FAILED"; + +# 16 times \x00 +assert LDAP::utf16_guid_to_hex_repr(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") == "00000000-0000-0000-0000-000000000000"; + +# 16 times \xff +assert LDAP::utf16_guid_to_hex_repr(b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") == "ffffffff-ffff-ffff-ffff-ffffffffffff"; + +# Valid DomainGuidFilter +assert LDAP::utf16_guid_to_hex_repr(b"\x3b\x52\xb3\xb0\x6f\x54\xaf\x4f\x93\xb2\x29\x4a\x38\x50\x98\xf2") == "b0b3523b-546f-4faf-93b2-294a385098f2"; + +# ---------------------------------------------------------------------------------- +# function bytes_sid_to_hex_repr() +# - transforms bytes of arbitrary length to a ':' separated string + +# Empty +assert LDAP::bytes_sid_to_hex_repr(b"") == ""; + +# 10 times \x00 +assert LDAP::bytes_sid_to_hex_repr(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") == "00:00:00:00:00:00:00:00:00:00"; + +# 10 times \xff +assert LDAP::bytes_sid_to_hex_repr(b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") == "ff:ff:ff:ff:ff:ff:ff:ff:ff:ff"; + +# Valid `AAC` value +assert LDAP::bytes_sid_to_hex_repr(b"\x80\x00\x00\x00") == "80:00:00:00"; + +# Valid objectSid +assert LDAP::bytes_sid_to_hex_repr(b"\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xd5\x64\xbe\x81\x5d\x68\x9c\x0d\x44\x4a\xae\x74\x01\x02\x00\x00") == "01:05:00:00:00:00:00:05:15:00:00:00:d5:64:be:81:5d:68:9c:0d:44:4a:ae:74:01:02:00:00"; + +# ---------------------------------------------------------------------------------- +# function bytes_sid_to_SID_repr() +# - requires exactly 24 bytes + +# Not enough bytes (0 and 10) +assert LDAP::bytes_sid_to_SID_repr(b"") == "SID_FORMAT_FAILED"; +assert LDAP::bytes_sid_to_SID_repr(b"1234567890") == "SID_FORMAT_FAILED"; + +# Too much bytes (25) +assert LDAP::bytes_sid_to_SID_repr(b"1234567890123456789012345") == "SID_FORMAT_FAILED"; + +# Empty +assert LDAP::bytes_sid_to_SID_repr(b"") == "SID_FORMAT_FAILED"; + +# Valid SID +assert LDAP::bytes_sid_to_SID_repr(b"\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x39\xc5\xc7\x44\xfa\xbd\x24\x1d\x4a\x65\xfd\x71") == "S-1-5-21-1153942841-488947194-1912431946"; + +# Some random bytes - probably an invalid SID but no error +assert LDAP::bytes_sid_to_SID_repr(b"\x02\x08\x00\x02\x00\x00\x00\x05\x15\x20\x00\x12\xd5\x64\xaf\x84\x5d\x68\x9c\x0d\x44\x4c\xad\x73") == "S-2-8589934597-301998101-2226087125-228354141-1940737092"; + +# All 1's +assert LDAP::bytes_sid_to_SID_repr(b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") == "S-255-281474976710655-4294967295-4294967295-4294967295-4294967295"; + +# ---------------------------------------------------------------------------------- +# function uint32_to_hex_repr() + +# Not enough bytes (0 and 2) +assert LDAP::uint32_to_hex_repr(b"") == "HEX_FORMAT_FAILED"; +assert LDAP::uint32_to_hex_repr(b"12") == "HEX_FORMAT_FAILED"; + +# Too much bytes (6) +assert LDAP::uint32_to_hex_repr(b"123456") == "HEX_FORMAT_FAILED"; + +# Empty +assert LDAP::uint32_to_hex_repr(b"") == "HEX_FORMAT_FAILED"; + +# Valid `NtVer` value +assert LDAP::uint32_to_hex_repr(b"\x16\x00\x00\x00") == "0x00000016"; + +# 4 times \x00 +assert LDAP::uint32_to_hex_repr(b"\x00\x00\x00\x00") == "0x00000000"; + +# 4 times \xff +assert LDAP::uint32_to_hex_repr(b"\xff\xff\xff\xff") == "0xffffffff"; + +# ---------------------------------------------------------------------------------- +# function string_representation() +function make_nested_repr(filters: vector): string { + local nestedOr: LDAP::ParseNestedAndOr; + nestedOr.searchfilters = vector(); + + for (f in filters) { + local or_: LDAP::SearchFilter; + or_.filterType = LDAP::FilterType::FILTER_PRESENT; + or_.FILTER_PRESENT = f; + or_.stringRepresentation = LDAP::string_representation(or_); + + nestedOr.searchfilters.push_back(or_); + } + + local searchFilter: LDAP::SearchFilter; + searchFilter.filterType = LDAP::FilterType::FILTER_OR; + searchFilter.FILTER_OR = nestedOr; + + return LDAP::string_representation(searchFilter); +} + +function test_string_representation() { + local repr0 = make_nested_repr(vector()); + assert repr0 == "": repr0; + + local repr1 = make_nested_repr(vector("foo")); + assert repr1 == "(|(foo=*))": repr1; + + local repr2 = make_nested_repr(vector("foo", "bar")); + assert repr2 == "(|(foo=*)(bar=*))": repr2; + + local repr3 = make_nested_repr(vector("foo", "bar", "baz")); + assert repr3 == "(|(|(foo=*)(bar=*))(baz=*))": repr3; +} + +test_string_representation(); diff --git a/testing/btest/scripts/base/protocols/ldap/log_policy.zeek b/testing/btest/scripts/base/protocols/ldap/log_policy.zeek new file mode 100644 index 0000000000..b12adc8ebd --- /dev/null +++ b/testing/btest/scripts/base/protocols/ldap/log_policy.zeek @@ -0,0 +1,22 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +# @TEST-REQUIRES: have-spicy +# @TEST-EXEC: zeek -C -r ${TRACES}/ldap/simpleauth.pcap %INPUT >output 2>&1 +# @TEST-EXEC: btest-diff output +# @TEST-EXEC: cat conn.log | zeek-cut -Cn local_orig local_resp > conn.log2 && mv conn.log2 conn.log +# @TEST-EXEC: btest-diff conn.log +# @TEST-EXEC: ! test -f ldap.log +# @TEST-EXEC: ! test -f ldap_search.log +# +# @TEST-DOC: Test LDAP analyzer with small trace using logging policies. + +hook LDAP::log_policy(rec: LDAP::MessageInfo, id: Log::ID, filter: Log::Filter) + { + break; + } + +hook LDAP::log_policy_search(rec: LDAP::SearchInfo, id: Log::ID, + filter: Log::Filter) + { + break; + } diff --git a/testing/btest/scripts/base/protocols/ldap/sasl-encrypted.zeek b/testing/btest/scripts/base/protocols/ldap/sasl-encrypted.zeek new file mode 100644 index 0000000000..9e217bccb4 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ldap/sasl-encrypted.zeek @@ -0,0 +1,12 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +# @TEST-REQUIRES: have-spicy +# @TEST-EXEC: zeek -C -r ${TRACES}/ldap/krb5-sign-seal-01.pcap %INPUT +# @TEST-EXEC: cat conn.log | zeek-cut -Cn local_orig local_resp > conn.log2 && mv conn.log2 conn.log +# @TEST-EXEC: btest-diff conn.log +# @TEST-EXEC: btest-diff ldap.log +# @TEST-EXEC: btest-diff ldap_search.log +# @TEST-EXEC: ! test -f weird.log +# @TEST-EXEC: ! test -f dpd.log +# +# @TEST-DOC: Test LDAP analyzer with SASL encrypted payloads. diff --git a/testing/btest/scripts/base/protocols/ldap/search_filter_extended.zeek b/testing/btest/scripts/base/protocols/ldap/search_filter_extended.zeek new file mode 100644 index 0000000000..f5c1e82378 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ldap/search_filter_extended.zeek @@ -0,0 +1,10 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +# @TEST-DOC: This test case is a regression test for #23. +# +# @TEST-REQUIRES: have-spicy +# @TEST-EXEC: zeek -C -r ${TRACES}/ldap/issue-32.pcapng %INPUT +# @TEST-EXEC: cat ldap_search.log | zeek-cut -C uid filter base_objects > ldap_search.log2 && mv ldap_search.log2 ldap_search.log +# @TEST-EXEC: btest-diff ldap_search.log +# +# @TEST-DOC: Test LDAP analyzer with small trace. diff --git a/testing/external/commit-hash.zeek-testing-private b/testing/external/commit-hash.zeek-testing-private index 2772323aa6..b8c4531792 100644 --- a/testing/external/commit-hash.zeek-testing-private +++ b/testing/external/commit-hash.zeek-testing-private @@ -1 +1 @@ -e2bcf374b7f03f95f7280568eab31600d58812ae +1522faf244d4de213a8f17ab4e2c5273d80d99e4