mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00
Merge remote-tracking branch 'origin/topic/bbannier/issue-3234'
* origin/topic/bbannier/issue-3234: Introduce dedicated `LDAP::Info` Remove redundant storing of protocol in LDAP logs Use LDAP `RemovalHook` instead of implementing `connection_state_remove` Tidy up LDAP code by using local references Pluralize container names in LDAP types Move LDAP script constants to their own file Name `LDAP::Message` and `LDAP::Search` `*Info` Make ports for LDAP analyzers fully configurable Require have-spicy for tests which log spicy-ldap information Fix LDAP analyzer setup for when Spicy analyzers are disabled Bump zeek-testing-private Integrate spicy-ldap test suite Move spicy-ldap into Zeek protocol analyzer tree Explicitly use all of spicy-ldap's modules Explicitly list `asn1.spicy` as spicy-ldap source Remove uses of `zeek` module in spicy-ldap Fix typos in spicy-ldap Remove project configuration files in spicy-ldap Integrate spicy-ldap into build Import zeek/spicy-ldap@57b5eff988
This commit is contained in:
commit
72df1a0216
53 changed files with 2274 additions and 17 deletions
|
@ -69,7 +69,8 @@
|
||||||
"kwargs": {
|
"kwargs": {
|
||||||
"NAME": "*",
|
"NAME": "*",
|
||||||
"PACKAGE_NAME": "*",
|
"PACKAGE_NAME": "*",
|
||||||
"SOURCES": "*"
|
"SOURCES": "*",
|
||||||
|
"MODULES": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,3 +69,5 @@ caf = "caf"
|
||||||
helo = "helo"
|
helo = "helo"
|
||||||
# Seems we use this in the management framework
|
# Seems we use this in the management framework
|
||||||
requestor = "requestor"
|
requestor = "requestor"
|
||||||
|
# `inout` is used as a keyword in Spicy, but looks like a typo of `input`.
|
||||||
|
inout = "inout"
|
||||||
|
|
86
CHANGES
86
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
|
6.1.0-dev.502 | 2023-10-09 16:26:58 +0200
|
||||||
|
|
||||||
* btest/plugins/hooks: Run in bare mode (Arne Welzel, Corelight)
|
* btest/plugins/hooks: Run in bare mode (Arne Welzel, Corelight)
|
||||||
|
|
19
NEWS
19
NEWS
|
@ -29,6 +29,25 @@ Breaking Changes
|
||||||
New Functionality
|
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
|
- Added a new ``assert`` statement for assertion based testing and asserting
|
||||||
runtime state.
|
runtime state.
|
||||||
|
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
6.1.0-dev.502
|
6.1.0-dev.528
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
@load base/protocols/imap
|
@load base/protocols/imap
|
||||||
@load base/protocols/irc
|
@load base/protocols/irc
|
||||||
@load base/protocols/krb
|
@load base/protocols/krb
|
||||||
|
@load base/protocols/ldap
|
||||||
@load base/protocols/modbus
|
@load base/protocols/modbus
|
||||||
@load base/protocols/mqtt
|
@load base/protocols/mqtt
|
||||||
@load base/protocols/mysql
|
@load base/protocols/mysql
|
||||||
|
|
5
scripts/base/protocols/ldap/__load__.zeek
Normal file
5
scripts/base/protocols/ldap/__load__.zeek
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
@if ( have_spicy_analyzers() )
|
||||||
|
@load-sigs ./dpd.sig
|
||||||
|
@load ./consts
|
||||||
|
@load ./main.zeek
|
||||||
|
@endif
|
123
scripts/base/protocols/ldap/consts.zeek
Normal file
123
scripts/base/protocols/ldap/consts.zeek
Normal file
|
@ -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";
|
||||||
|
}
|
23
scripts/base/protocols/ldap/dpd.sig
Normal file
23
scripts/base/protocols/ldap/dpd.sig
Normal file
|
@ -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"
|
||||||
|
}
|
368
scripts/base/protocols/ldap/main.zeek
Normal file
368
scripts/base/protocols/ldap/main.zeek
Normal file
|
@ -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 <method>" 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 <method>" 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ add_subdirectory(ident)
|
||||||
add_subdirectory(imap)
|
add_subdirectory(imap)
|
||||||
add_subdirectory(irc)
|
add_subdirectory(irc)
|
||||||
add_subdirectory(krb)
|
add_subdirectory(krb)
|
||||||
|
add_subdirectory(ldap)
|
||||||
add_subdirectory(login)
|
add_subdirectory(login)
|
||||||
add_subdirectory(mime)
|
add_subdirectory(mime)
|
||||||
add_subdirectory(modbus)
|
add_subdirectory(modbus)
|
||||||
|
|
5
src/analyzer/protocol/ldap/CMakeLists.txt
Normal file
5
src/analyzer/protocol/ldap/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
spicy_add_analyzer(
|
||||||
|
NAME LDAP
|
||||||
|
PACKAGE_NAME spicy-ldap
|
||||||
|
SOURCES ldap.spicy ldap.evt asn1.spicy
|
||||||
|
MODULES LDAP ASN1)
|
278
src/analyzer/protocol/ldap/asn1.spicy
Normal file
278
src/analyzer/protocol/ldap/asn1.spicy
Normal file
|
@ -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<bool>($$);
|
||||||
|
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<uint64>;
|
||||||
|
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<bool>($$) &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<int32>(self.head.tag.type_);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
40
src/analyzer/protocol/ldap/ldap.evt
Normal file
40
src/analyzer/protocol/ldap/ldap.evt
Normal file
|
@ -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);
|
875
src/analyzer/protocol/ldap/ldap.spicy
Normal file
875
src/analyzer/protocol/ldap/ldap.spicy
Normal file
|
@ -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<ResultCode>(cast<uint8>($$.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<ProtocolOpcode>(cast<uint8>($$.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<BindAuthType>(cast<uint8>($$.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<string>;
|
||||||
|
|
||||||
|
# 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<FilterType>(cast<uint8>($$.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<SearchScope>(cast<uint8>($$.body.num_value))
|
||||||
|
&default=SearchScope::Undef {
|
||||||
|
message.arg = "%s" % self.scope;
|
||||||
|
}
|
||||||
|
deref: ASN1::ASN1Message(True) &convert=cast<SearchDerefAlias>(cast<uint8>($$.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();
|
||||||
|
}
|
|
@ -18,9 +18,11 @@
|
||||||
1 25
|
1 25
|
||||||
1 2811
|
1 2811
|
||||||
1 3128
|
1 3128
|
||||||
|
1 3268
|
||||||
1 3306
|
1 3306
|
||||||
2 3389
|
2 3389
|
||||||
1 3544
|
1 3544
|
||||||
|
2 389
|
||||||
1 4011
|
1 4011
|
||||||
2 443
|
2 443
|
||||||
1 445
|
1 445
|
||||||
|
@ -60,8 +62,8 @@
|
||||||
1 992
|
1 992
|
||||||
1 993
|
1 993
|
||||||
1 995
|
1 995
|
||||||
67 and
|
70 and
|
||||||
66 or
|
69 or
|
||||||
67 port
|
70 port
|
||||||
44 tcp
|
46 tcp
|
||||||
23 udp
|
24 udp
|
||||||
|
|
|
@ -406,6 +406,9 @@ scripts/base/init-default.zeek
|
||||||
scripts/base/protocols/krb/main.zeek
|
scripts/base/protocols/krb/main.zeek
|
||||||
scripts/base/protocols/krb/consts.zeek
|
scripts/base/protocols/krb/consts.zeek
|
||||||
scripts/base/protocols/krb/files.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/__load__.zeek
|
||||||
scripts/base/protocols/modbus/consts.zeek
|
scripts/base/protocols/modbus/consts.zeek
|
||||||
scripts/base/protocols/modbus/main.zeek
|
scripts/base/protocols/modbus/main.zeek
|
||||||
|
|
|
@ -20,6 +20,8 @@ known_certs
|
||||||
known_hosts
|
known_hosts
|
||||||
known_modbus
|
known_modbus
|
||||||
known_services
|
known_services
|
||||||
|
ldap
|
||||||
|
ldap_search
|
||||||
loaded_scripts
|
loaded_scripts
|
||||||
modbus
|
modbus
|
||||||
modbus_register_change
|
modbus_register_change
|
||||||
|
|
|
@ -360,6 +360,39 @@ connection {
|
||||||
* ts: time, log=T, optional=F
|
* ts: time, log=T, optional=F
|
||||||
* uid: string, 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: record Modbus::Info, log=F, optional=T
|
||||||
Modbus::Info {
|
Modbus::Info {
|
||||||
* exception: string, log=T, optional=T
|
* exception: string, log=T, optional=T
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
#open XXXX-XX-XX-XX-XX-XX
|
#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
|
#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
|
#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 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 SHA256,X509,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 1032 - 0 0 F - 9e4ac96474245129d9766700412a1f89 d83c1a7f4d0446bb2081b81a1670f8183451ca24 a047a37fa2d2e118a4f5095fe074d6cfe0e352425a7632bf8659c03919a6c81d
|
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 SHA256,X509,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 897 - 0 0 F - 2e7db2a31d0e3da4b25f49b9542a2e1a 7359755c6df9a0abc3060bce369564c8ec4542a3 3c35cc963eb004451323d3275d05b353235053490d9cd83729a2faf5e7ca1cc0
|
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 SHA256,X509,SHA1,MD5 application/x-x509-user-cert - 0.000000 F F 1859 - 0 0 F - 7af07aca6d5c6e8e87fe4bb34786edc0 548b9e03bc183d1cd39f93a37985cb3950f8f06f 6bacfa4536150ed996f2b0c05ab6e345a257225f449aeb9d2018ccd88f4ede43
|
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 SHA256,X509,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 1032 - 0 0 F - 9e4ac96474245129d9766700412a1f89 d83c1a7f4d0446bb2081b81a1670f8183451ca24 a047a37fa2d2e118a4f5095fe074d6cfe0e352425a7632bf8659c03919a6c81d
|
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 SHA256,X509,SHA1,MD5 application/x-x509-ca-cert - 0.000000 F F 897 - 0 0 F - 2e7db2a31d0e3da4b25f49b9542a2e1a 7359755c6df9a0abc3060bce369564c8ec4542a3 3c35cc963eb004451323d3275d05b353235053490d9cd83729a2faf5e7ca1cc0
|
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
|
#close XXXX-XX-XX-XX-XX-XX
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -6,4 +6,15 @@ depend on them for tests.
|
||||||
|
|
||||||
Trace Index/Sources:
|
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.
|
- 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
|
||||||
|
<https://wiki.wireshark.org/uploads/__moin_import__/attachments/SampleCaptures/ldap-krb5-sign-seal-01.cap>
|
||||||
|
- 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,
|
||||||
|
<https://github.com/zeek/spicy-ldap/issues/23>
|
||||||
|
|
BIN
testing/btest/Traces/ldap/issue-32.pcapng
Normal file
BIN
testing/btest/Traces/ldap/issue-32.pcapng
Normal file
Binary file not shown.
BIN
testing/btest/Traces/ldap/krb5-sign-seal-01.pcap
Normal file
BIN
testing/btest/Traces/ldap/krb5-sign-seal-01.pcap
Normal file
Binary file not shown.
BIN
testing/btest/Traces/ldap/simpleauth-diff-port.pcap
Normal file
BIN
testing/btest/Traces/ldap/simpleauth-diff-port.pcap
Normal file
Binary file not shown.
BIN
testing/btest/Traces/ldap/simpleauth.pcap
Normal file
BIN
testing/btest/Traces/ldap/simpleauth.pcap
Normal file
Binary file not shown.
|
@ -1,3 +1,5 @@
|
||||||
|
# @TEST-REQUIRES: have-spicy
|
||||||
|
#
|
||||||
# @TEST-EXEC: zeek -r $TRACES/empty.trace >output
|
# @TEST-EXEC: zeek -r $TRACES/empty.trace >output
|
||||||
# @TEST-EXEC: cat packet_filter.log >>output
|
# @TEST-EXEC: cat packet_filter.log >>output
|
||||||
# @TEST-EXEC: zeek -r $TRACES/empty.trace -f "port 42" >>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: cat packet_filter.log >>output
|
||||||
# @TEST-EXEC: btest-diff output
|
# @TEST-EXEC: btest-diff output
|
||||||
# @TEST-EXEC: btest-diff conn.log
|
# @TEST-EXEC: btest-diff conn.log
|
||||||
#
|
#
|
||||||
# The order in the output of enable_auto_protocol_capture_filters isn't
|
# The order in the output of enable_auto_protocol_capture_filters isn't
|
||||||
# stable, for reasons not clear. We canonify it first.
|
# 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: 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: 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
|
# @TEST-EXEC: btest-diff output2
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#
|
#
|
||||||
# If this test fails, then the "Log Files" documentation page should be updated.
|
# If this test fails, then the "Log Files" documentation page should be updated.
|
||||||
|
|
||||||
|
# @TEST-REQUIRES: ${SCRIPTS}/have-spicy
|
||||||
# @TEST-REQUIRES: which python3
|
# @TEST-REQUIRES: which python3
|
||||||
# @TEST-EXEC: bash %INPUT
|
# @TEST-EXEC: bash %INPUT
|
||||||
# @TEST-EXEC: btest-diff out
|
# @TEST-EXEC: btest-diff out
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Test that checks that files.log is generated if the respective option is set.
|
# 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: zeek -b -r $TRACES/tls/google-duplicate.trace %INPUT
|
||||||
# @TEST-EXEC: btest-diff files.log
|
# @TEST-EXEC: btest-diff files.log
|
||||||
|
|
||||||
|
|
12
testing/btest/scripts/base/protocols/ldap/attributes.zeek
Normal file
12
testing/btest/scripts/base/protocols/ldap/attributes.zeek
Normal file
|
@ -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;
|
|
@ -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.
|
11
testing/btest/scripts/base/protocols/ldap/basic.zeek
Normal file
11
testing/btest/scripts/base/protocols/ldap/basic.zeek
Normal file
|
@ -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.
|
10
testing/btest/scripts/base/protocols/ldap/diff_port.zeek
Normal file
10
testing/btest/scripts/base/protocols/ldap/diff_port.zeek
Normal file
|
@ -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.
|
134
testing/btest/scripts/base/protocols/ldap/functions.spicy
Normal file
134
testing/btest/scripts/base/protocols/ldap/functions.spicy
Normal file
|
@ -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>): string {
|
||||||
|
local nestedOr: LDAP::ParseNestedAndOr;
|
||||||
|
nestedOr.searchfilters = vector<LDAP::SearchFilter>();
|
||||||
|
|
||||||
|
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();
|
22
testing/btest/scripts/base/protocols/ldap/log_policy.zeek
Normal file
22
testing/btest/scripts/base/protocols/ldap/log_policy.zeek
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -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.
|
|
@ -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.
|
|
@ -1 +1 @@
|
||||||
e2bcf374b7f03f95f7280568eab31600d58812ae
|
1522faf244d4de213a8f17ab4e2c5273d80d99e4
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue