detect: add ldap.responses.result_code

ldap.responses.result_code matches on LDAP result code
This keyword maps the following eve fields:
ldap.responses[].bind_response.result_code
ldap.responses[].search_result_done.result_code
ldap.responses[].modify_response.result_code
ldap.responses[].add_response.result_code
ldap.responses[].del_response.result_code
ldap.responses[].mod_dn_response.result_code
ldap.responses[].compare_response.result_code
ldap.responses[].extended_response.result_code
It is an unsigned 32-bit integer
Doesn't support prefiltering

Ticket: #7532
pull/12717/head
Alice Akaki 9 months ago committed by Victor Julien
parent d827728661
commit 84605db01d

@ -259,3 +259,159 @@ and contains the LDAP distinguished name ``dc=example,dc=com``.
.. container:: example-rule
alert ldap any any -> any any (msg:"Test LDAPDN and operation"; :example-rule-emphasis:`ldap.responses.operation:search_result_entry,1; ldap.responses.dn; content:"dc=example,dc=com";` sid:1;)
ldap.responses.result_code
--------------------------
Suricata has a ``ldap.responses.result_code`` keyword that can be used in signatures to identify
and filter network packets based on their LDAP result code.
Syntax::
ldap.responses.result_code: code[,index];
ldap.responses.result_code uses :ref:`unsigned 32-bit integer <rules-integer-keywords>`.
This keyword maps to the following eve fields:
- ``ldap.responses[].bind_response.result_code``
- ``ldap.responses[].search_result_done.result_code``
- ``ldap.responses[].modify_response.result_code``
- ``ldap.responses[].add_response.result_code``
- ``ldap.responses[].del_response.result_code``
- ``ldap.responses[].mod_dn_response.result_code``
- ``ldap.responses[].compare_response.result_code``
- ``ldap.responses[].extended_response.result_code``
.. table:: **Result code values for ldap.responses.result_code**
========= ================================================
Code Name
========= ================================================
0 success
1 operations_error
2 protocol_error
3 time_limit_exceeded
4 size_limit_exceeded
5 compare_false
6 compare_true
7 auth_method_not_supported
8 stronger_auth_required
10 referral
11 admin_limit_exceeded
12 unavailable_critical_extension
13 confidentiality_required
14 sasl_bind_in_progress
16 no_such_attribute
17 undefined_attribute_type
18 inappropriate_matching
19 constraint_violation
20 attribute_or_value_exists
21 invalid_attribute_syntax
32 no_such_object
33 alias_problem
34 invalid_dns_syntax
35 is_leaf
36 alias_dereferencing_problem
48 inappropriate_authentication
49 invalid_credentials
50 insufficient_access_rights
51 busy
52 unavailable
53 unwilling_to_perform
54 loop_detect
60 sort_control_missing
61 offset_range_error
64 naming_violation
65 object_class_violation
66 not_allowed_on_non_leaf
67 not_allowed_on_rdn
68 entry_already_exists
69 object_class_mods_prohibited
70 results_too_large
71 affects_multiple_dsas
76 control_error
80 other
81 server_down
82 local_error
83 encoding_error
84 decoding_error
85 timeout
86 auth_unknown
87 filter_error
88 user_canceled
89 param_error
90 no_memory
91 connect_error
92 not_supported
93 control_not_found
94 no_results_returned
95 more_results_to_return
96 client_loop
97 referral_limit_exceeded
100 invalid_response
101 ambiguous_response
112 tls_not_supported
113 intermediate_response
114 unknown_type
118 canceled
119 no_such_operation
120 too_late
121 cannot_cancel
122 assertion_failed
123 authorization_denied
4096 e_sync_refresh_required
16654 no_operation
========= ================================================
More information about LDAP result code values can be found here:
https://ldap.com/ldap-result-code-reference/
An LDAP request operation can receive multiple responses. By default, the ldap.responses.result_code
keyword matches with any indices, but it is possible to specify a particular index for matching
and also use flags such as ``all`` and ``any``.
.. table:: **Index values for ldap.responses.result_code keyword**
========= ================================================
Value Description
========= ================================================
[default] Match with any index
all Match only if all indexes match
any Match with any index
0>= Match specific index
0< Match specific index with back to front indexing
========= ================================================
Examples
^^^^^^^^
Example of signatures that would alert if the packet has a ``success`` LDAP result code at any index:
.. container:: example-rule
alert ldap any any -> any any (msg:"Test LDAP result code"; :example-rule-emphasis:`ldap.responses.result_code:0;` sid:1;)
.. container:: example-rule
alert ldap any any -> any any (msg:"Test LDAP result code"; :example-rule-emphasis:`ldap.responses.result_code:success,any;` sid:1;)
Example of a signature that would alert if the packet has an ``unavailable`` LDAP result code at index 1:
.. container:: example-rule
alert ldap any any -> any any (msg:"Test LDAP result code at index 1"; :example-rule-emphasis:`ldap.responses.result_code:unavailable,1;` sid:1;)
Example of a signature that would alert if all the responses have a ``success`` LDAP result code:
.. container:: example-rule
alert ldap any any -> any any (msg:"Test all LDAP responses have success result code"; :example-rule-emphasis:`ldap.responses.result_code:success,all;` sid:1;)
The keyword ldap.responses.result_code supports back to front indexing with negative numbers,
this means that -1 will represent the last index, -2 the second to last index, and so on.
This is an example of a signature that would alert if a ``success`` result code is found at the last index:
.. container:: example-rule
alert ldap any any -> any any (msg:"Test LDAP success at last index"; :example-rule-emphasis:`ldap.responses.result_code:success,-1;` sid:1;)

@ -26,7 +26,7 @@ use crate::detect::{
DetectHelperMultiBufferMpmRegister, DetectSignatureSetAppProto, SCSigTableElmt,
SigMatchAppendSMToList, SIGMATCH_INFO_STICKY_BUFFER, SIGMATCH_NOOPT,
};
use crate::ldap::types::{LdapMessage, ProtocolOp, ProtocolOpCode};
use crate::ldap::types::{LdapMessage, LdapResultCode, ProtocolOp, ProtocolOpCode};
use std::collections::VecDeque;
use std::ffi::CStr;
@ -50,6 +50,15 @@ struct DetectLdapRespOpData {
pub index: LdapIndex,
}
struct DetectLdapRespResultData {
/// Ldap result code
pub du32: DetectUintData<u32>,
/// Index can be Any to match with any responses index,
/// All to match if all indices, or an i32 integer
/// Negative values represent back to front indexing.
pub index: LdapIndex,
}
static mut G_LDAP_REQUEST_OPERATION_KW_ID: c_int = 0;
static mut G_LDAP_REQUEST_OPERATION_BUFFER_ID: c_int = 0;
static mut G_LDAP_RESPONSES_OPERATION_KW_ID: c_int = 0;
@ -58,6 +67,8 @@ static mut G_LDAP_RESPONSES_COUNT_KW_ID: c_int = 0;
static mut G_LDAP_RESPONSES_COUNT_BUFFER_ID: c_int = 0;
static mut G_LDAP_REQUEST_DN_BUFFER_ID: c_int = 0;
static mut G_LDAP_RESPONSES_DN_BUFFER_ID: c_int = 0;
static mut G_LDAP_RESPONSES_RESULT_CODE_KW_ID: c_int = 0;
static mut G_LDAP_RESPONSES_RESULT_CODE_BUFFER_ID: c_int = 0;
unsafe extern "C" fn ldap_parse_protocol_req_op(
ustr: *const std::os::raw::c_char,
@ -402,6 +413,92 @@ unsafe extern "C" fn ldap_tx_get_responses_dn(
return true;
}
fn aux_ldap_parse_resp_result_code(s: &str) -> Option<DetectLdapRespResultData> {
let parts: Vec<&str> = s.split(',').collect();
if parts.len() > 2 {
return None;
}
let index = parse_ldap_index(&parts)?;
let du32 = detect_parse_uint_enum::<u32, LdapResultCode>(parts[0])?;
Some(DetectLdapRespResultData { du32, index })
}
unsafe extern "C" fn ldap_parse_responses_result_code(
ustr: *const std::os::raw::c_char,
) -> *mut DetectUintData<u32> {
let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
if let Ok(s) = ft_name.to_str() {
if let Some(ctx) = aux_ldap_parse_resp_result_code(s) {
let boxed = Box::new(ctx);
return Box::into_raw(boxed) as *mut _;
}
}
return std::ptr::null_mut();
}
unsafe extern "C" fn ldap_detect_responses_result_code_setup(
de: *mut c_void, s: *mut c_void, raw: *const libc::c_char,
) -> c_int {
if DetectSignatureSetAppProto(s, ALPROTO_LDAP) != 0 {
return -1;
}
let ctx = ldap_parse_responses_result_code(raw) as *mut c_void;
if ctx.is_null() {
return -1;
}
if SigMatchAppendSMToList(
de,
s,
G_LDAP_RESPONSES_RESULT_CODE_KW_ID,
ctx,
G_LDAP_RESPONSES_RESULT_CODE_BUFFER_ID,
)
.is_null()
{
ldap_detect_responses_result_code_free(std::ptr::null_mut(), ctx);
return -1;
}
return 0;
}
fn get_ldap_result_code(response: &LdapMessage) -> Option<u32> {
return match &response.protocol_op {
ProtocolOp::BindResponse(resp) => Some(resp.result.result_code.0),
ProtocolOp::SearchResultDone(resp) => Some(resp.result_code.0),
ProtocolOp::ModifyResponse(resp) => Some(resp.result.result_code.0),
ProtocolOp::AddResponse(resp) => Some(resp.result_code.0),
ProtocolOp::DelResponse(resp) => Some(resp.result_code.0),
ProtocolOp::ModDnResponse(resp) => Some(resp.result_code.0),
ProtocolOp::CompareResponse(resp) => Some(resp.result_code.0),
ProtocolOp::ExtendedResponse(resp) => Some(resp.result.result_code.0),
_ => None,
};
}
unsafe extern "C" fn ldap_detect_responses_result_code_match(
_de: *mut c_void, _f: *mut c_void, _flags: u8, _state: *mut c_void, tx: *mut c_void,
_sig: *const c_void, ctx: *const c_void,
) -> c_int {
let tx = cast_pointer!(tx, LdapTransaction);
let ctx = cast_pointer!(ctx, DetectLdapRespResultData);
return match_at_index::<LdapMessage, u32>(
&tx.responses,
&ctx.du32,
get_ldap_result_code,
|code, ctx_value| detect_match_uint(ctx_value, code) as c_int,
&ctx.index,
);
}
unsafe extern "C" fn ldap_detect_responses_result_code_free(_de: *mut c_void, ctx: *mut c_void) {
// Just unbox...
let ctx = cast_pointer!(ctx, DetectLdapRespResultData);
std::mem::drop(Box::from_raw(ctx));
}
#[no_mangle]
pub unsafe extern "C" fn SCDetectLdapRegister() {
let kw = SCSigTableElmt {
@ -489,4 +586,21 @@ pub unsafe extern "C" fn SCDetectLdapRegister() {
false, //to server
ldap_detect_responses_dn_get_data,
);
let kw = SCSigTableElmt {
name: b"ldap.responses.result_code\0".as_ptr() as *const libc::c_char,
desc: b"match LDAPResult code\0".as_ptr() as *const libc::c_char,
url: b"/rules/ldap-keywords.html#ldap.responses.result_code\0".as_ptr()
as *const libc::c_char,
AppLayerTxMatch: Some(ldap_detect_responses_result_code_match),
Setup: ldap_detect_responses_result_code_setup,
Free: Some(ldap_detect_responses_result_code_free),
flags: 0,
};
G_LDAP_RESPONSES_RESULT_CODE_KW_ID = DetectHelperKeywordRegister(&kw);
G_LDAP_RESPONSES_RESULT_CODE_BUFFER_ID = DetectHelperBufferRegister(
b"ldap.responses.result_code\0".as_ptr() as *const libc::c_char,
ALPROTO_LDAP,
true, //to client
false, //to server
);
}

@ -17,6 +17,7 @@
// written by Giuseppe Longo <giuseppe@glongo.it>
use crate::detect::EnumString;
use crate::jsonbuilder::{JsonBuilder, JsonError};
use crate::ldap::filters::*;
use crate::ldap::ldap::LdapTransaction;
@ -319,7 +320,11 @@ fn log_intermediate_response(
}
fn log_ldap_result(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.set_string("result_code", &msg.result_code.to_string())?;
if let Some(rc) = LdapResultCode::from_u(msg.result_code.0) {
js.set_string("result_code", rc.to_str())?;
} else {
js.set_string("result_code", &format!("unknown-{}", msg.result_code.0))?;
}
js.set_string("matched_dn", &msg.matched_dn.0)?;
js.set_string("message", &msg.diagnostic_message.0)?;
Ok(())

@ -43,88 +43,6 @@ impl Display for Operation {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct ResultCode(pub u32);
impl Display for ResultCode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.0 {
0 => write!(f, "success"),
1 => write!(f, "operations_error"),
2 => write!(f, "protocol_error"),
3 => write!(f, "time_limit_exceeded"),
4 => write!(f, "size_limit_exceeded"),
5 => write!(f, "compare_false"),
6 => write!(f, "compare_true"),
7 => write!(f, "auth_method_not_supported"),
8 => write!(f, "stronger_auth_required"),
10 => write!(f, "referral"),
11 => write!(f, "admin_limit_exceeded"),
12 => write!(f, "unavailable_critical_extension"),
13 => write!(f, "confidentiality_required"),
14 => write!(f, "sasl_bind_in_progress"),
16 => write!(f, "no_such_attribute"),
17 => write!(f, "undefined_attribute_type"),
18 => write!(f, "inappropriate_matching"),
19 => write!(f, "constraint_violation"),
20 => write!(f, "attribute_or_value_exists"),
21 => write!(f, "invalid_attribute_syntax"),
32 => write!(f, "no_such_object"),
33 => write!(f, "alias_problem"),
34 => write!(f, "invalid_dns_syntax"),
35 => write!(f, "is_leaf"),
36 => write!(f, "alias_dereferencing_problem"),
48 => write!(f, "inappropriate_authentication"),
49 => write!(f, "invalid_credentials"),
50 => write!(f, "insufficient_access_rights"),
51 => write!(f, "busy"),
52 => write!(f, "unavailable"),
53 => write!(f, "unwilling_to_perform"),
54 => write!(f, "loop_detect"),
60 => write!(f, "sort_control_missing"),
61 => write!(f, "offset_range_error"),
64 => write!(f, "naming_violation"),
65 => write!(f, "object_class_violation"),
66 => write!(f, "not_allowed_on_non_leaf"),
67 => write!(f, "not_allowed_on_rdn"),
68 => write!(f, "entry_already_exists"),
69 => write!(f, "object_class_mods_prohibited"),
70 => write!(f, "results_too_large"),
71 => write!(f, "affects_multiple_dsas"),
76 => write!(f, "control_error"),
80 => write!(f, "other"),
81 => write!(f, "server_down"),
82 => write!(f, "local_error"),
83 => write!(f, "encoding_error"),
84 => write!(f, "decoding_error"),
85 => write!(f, "timeout"),
86 => write!(f, "auth_unknown"),
87 => write!(f, "filter_error"),
88 => write!(f, "user_canceled"),
89 => write!(f, "param_error"),
90 => write!(f, "no_memory"),
91 => write!(f, "connect_error"),
92 => write!(f, "not_supported"),
93 => write!(f, "control_not_found"),
94 => write!(f, "no_results_returned"),
95 => write!(f, "more_results_to_return"),
96 => write!(f, "client_loop"),
97 => write!(f, "referral_limit_exceeded"),
100 => write!(f, "invalid_response"),
101 => write!(f, "ambiguous_response"),
112 => write!(f, "tls_not_supported"),
113 => write!(f, "intermediate_response"),
114 => write!(f, "unknown_type"),
118 => write!(f, "canceled"),
119 => write!(f, "no_such_operation"),
120 => write!(f, "too_late"),
121 => write!(f, "cannot_cancel"),
122 => write!(f, "assertion_failed"),
123 => write!(f, "authorization_denied"),
4096 => write!(f, "e_sync_refresh_required"),
16654 => write!(f, "no_operation"),
_ => write!(f, "{}", self.0),
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct MessageID(pub u32);
@ -280,6 +198,85 @@ pub struct IntermediateResponse {
pub response_value: Option<Vec<u8>>,
}
#[derive(Clone, Debug, EnumStringU32)]
#[repr(u32)]
pub enum LdapResultCode {
Success = 0,
OperationsError = 1,
ProtocolError = 2,
TimeLimitExceeded = 3,
SizeLimitExceeded = 4,
CompareFalse = 5,
CompareTrue = 6,
AuthMethodNotSupported = 7,
StrongerAuthRequired = 8,
Referral = 10,
AdminLimitExceeded = 11,
UnavailableCriticalExtension = 12,
ConfidentialityRequired = 13,
SaslBindInProgress = 14,
NoSuchAttribute = 16,
UndefinedAttributeType = 17,
InappropriateMatching = 18,
ConstraintViolation = 19,
AttributeOrValueExists = 20,
InvalidAttributeSyntax = 21,
NoSuchObject = 32,
AliasProblem = 33,
InvalidDnsSyntax = 34,
IsLeaf = 35,
AliasDereferencingProblem = 36,
InappropriateAuthentication = 48,
InvalidCredentials = 49,
InsufficientAccessRights = 50,
Busy = 51,
Unavailable = 52,
UnwillingToPerform = 53,
LoopDetect = 54,
SortControlMissing = 60,
OffsetRangeError = 61,
NamingViolation = 64,
ObjectClassViolation = 65,
NotAllowedOnNonLeaf = 66,
NotAllowedOnRdn = 67,
EntryAlreadyExists = 68,
ObjectClassModsProhibited = 69,
ResultsTooLarge = 70,
AffectsMultipleDsas = 71,
ControlError = 76,
Other = 80,
ServerDown = 81,
LocalError = 82,
EncodingError = 83,
DecodingError = 84,
Timeout = 85,
AuthUnknown = 86,
FilterError = 87,
UserCanceled = 88,
ParamError = 89,
NoMemory = 90,
ConnectError = 91,
NotSupported = 92,
ControlNotFound = 93,
NoResultsReturned = 94,
MoreResultsToReturn = 95,
ClientLoop = 96,
ReferralLimitExceeded = 97,
InvalidResponse = 100,
AmbiguousResponse = 101,
TlsNotSupported = 112,
IntermediateResponse = 113,
UnknownType = 114,
Canceled = 118,
NoSuchOperation = 119,
TooLate = 120,
CannotCancel = 121,
AssertionFailed = 122,
AuthorizationDenied = 123,
ESyncRefreshRequired = 4096,
NoOperation = 16654,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ProtocolOp {
BindRequest(BindRequest),

Loading…
Cancel
Save