From: Alice Akaki Date: Mon, 20 Jan 2025 18:12:02 +0000 (-0400) Subject: detect: add ldap.responses.operation X-Git-Tag: suricata-8.0.0-beta1~527 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=da593abd990566d7b47c274054c29f6b86240647;p=thirdparty%2Fsuricata.git detect: add ldap.responses.operation ldap.responses.operation matches on Lightweight Directory Access Protocol response operations This keyword maps to the eve field ldap.responses[].operation It is an unsigned 8-bit integer Doesn't support prefiltering Ticket: #7453 --- diff --git a/doc/userguide/rules/ldap-keywords.rst b/doc/userguide/rules/ldap-keywords.rst index 393255a88e..6d76b1cdf6 100644 --- a/doc/userguide/rules/ldap-keywords.rst +++ b/doc/userguide/rules/ldap-keywords.rst @@ -66,3 +66,66 @@ Example of a signatures that would alert if the packet has an LDAP bind request .. container:: example-rule alert tcp any any -> any any (msg:"Test LDAP bind request"; :example-rule-emphasis:`ldap.request.operation:bind_request;` sid:1;) + +ldap.responses.operation +------------------------ + +Suricata has a ``ldap.responses.operation`` keyword that can be used in signatures to identify +and filter network packets based on Lightweight Directory Access Protocol response operations. + +Syntax:: + + ldap.responses.operation: operation[,index]; + +ldap.responses.operation uses :ref:`unsigned 8-bit integer `. + +This keyword maps to the eve field ``ldap.responses[].operation`` + +An LDAP request operation can receive multiple responses. By default, the ldap.responses.operation +keyword matches all 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.operation 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 a signatures that would alert if the packet has an LDAP bind response operation: + +.. container:: example-rule + + alert tcp any any -> any any (msg:"Test LDAP bind response"; :example-rule-emphasis:`ldap.responses.operation:1;` sid:1;) + +.. container:: example-rule + + alert tcp any any -> any any (msg:"Test LDAP bind response"; :example-rule-emphasis:`ldap.responses.operation:bind_response;` sid:1;) + +Example of a signature that would alert if the packet has an LDAP search_result_done response operation at index 1: + +.. container:: example-rule + + alert tcp any any -> any any (msg:"Test LDAP search response"; :example-rule-emphasis:`ldap.responses.operation:search_result_done,1;` sid:1;) + +Example of a signature that would alert if all the responses are of type search_result_entry: + +.. container:: example-rule + + alert tcp any any -> any any (msg:"Test LDAP search response"; :example-rule-emphasis:`ldap.responses.operation:search_result_entry,all;` sid:1;) + +The keyword ldap.responses.operation 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 search_result_entry response is found at the last index: + +.. container:: example-rule + + alert tcp any any -> any any (msg:"Test LDAP search response"; :example-rule-emphasis:`ldap.responses.operation:search_result_entry,-1;` sid:1;) diff --git a/rust/src/ldap/detect.rs b/rust/src/ldap/detect.rs index 62161f0cb1..e77fc62d7e 100644 --- a/rust/src/ldap/detect.rs +++ b/rust/src/ldap/detect.rs @@ -23,13 +23,33 @@ use crate::detect::{ DetectHelperBufferRegister, DetectHelperKeywordRegister, DetectSignatureSetAppProto, SCSigTableElmt, SigMatchAppendSMToList, }; -use crate::ldap::types::ProtocolOpCode; +use crate::ldap::types::{LdapMessage, ProtocolOpCode}; use std::ffi::CStr; use std::os::raw::{c_int, c_void}; +use std::str::FromStr; + +#[derive(Debug, PartialEq)] +enum LdapIndex { + Any, + All, + Index(i32), +} + +#[derive(Debug, PartialEq)] +struct DetectLdapRespData { + /// Ldap response code + pub du8: DetectUintData, + /// 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; +static mut G_LDAP_RESPONSES_OPERATION_BUFFER_ID: c_int = 0; unsafe extern "C" fn ldap_parse_protocol_req_op( ustr: *const std::os::raw::c_char, @@ -88,6 +108,117 @@ unsafe extern "C" fn ldap_detect_request_free(_de: *mut c_void, ctx: *mut c_void rs_detect_u8_free(ctx); } +fn aux_ldap_parse_protocol_resp_op(s: &str) -> Option { + let parts: Vec<&str> = s.split(',').collect(); + if parts.len() > 2 { + return None; + } + let index = if parts.len() == 2 { + match parts[1] { + "all" => LdapIndex::All, + "any" => LdapIndex::Any, + _ => { + let i32_index = i32::from_str(parts[1]).ok()?; + LdapIndex::Index(i32_index) + } + } + } else { + LdapIndex::Any + }; + if let Some(ctx) = detect_parse_uint_enum::(parts[0]) { + let du8 = ctx; + return Some(DetectLdapRespData { du8, index }); + } + return None; +} + +unsafe extern "C" fn ldap_parse_protocol_resp_op( + ustr: *const std::os::raw::c_char, +) -> *mut DetectUintData { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Some(ctx) = aux_ldap_parse_protocol_resp_op(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_operation_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_protocol_resp_op(raw) as *mut c_void; + if ctx.is_null() { + return -1; + } + if SigMatchAppendSMToList( + de, + s, + G_LDAP_RESPONSES_OPERATION_KW_ID, + ctx, + G_LDAP_RESPONSES_OPERATION_BUFFER_ID, + ) + .is_null() + { + ldap_detect_responses_free(std::ptr::null_mut(), ctx); + return -1; + } + return 0; +} + +unsafe extern "C" fn ldap_detect_responses_operation_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, DetectLdapRespData); + + match ctx.index { + LdapIndex::Any => { + for response in &tx.responses { + let option: u8 = response.protocol_op.to_u8(); + if rs_detect_u8_match(option, &ctx.du8) == 1 { + return 1; + } + } + return 0; + } + LdapIndex::All => { + for response in &tx.responses { + let option: u8 = response.protocol_op.to_u8(); + if rs_detect_u8_match(option, &ctx.du8) == 0 { + return 0; + } + } + return 1; + } + LdapIndex::Index(idx) => { + let index = if idx < 0 { + // negative values for backward indexing. + ((tx.responses.len() as i32) + idx) as usize + } else { + idx as usize + }; + if tx.responses.len() <= index { + return 0; + } + let response: &LdapMessage = &tx.responses[index]; + let option: u8 = response.protocol_op.to_u8(); + return rs_detect_u8_match(option, &ctx.du8); + } + } +} + +unsafe extern "C" fn ldap_detect_responses_free(_de: *mut c_void, ctx: *mut c_void) { + // Just unbox... + let ctx = cast_pointer!(ctx, DetectLdapRespData); + std::mem::drop(Box::from_raw(ctx)); +} + #[no_mangle] pub unsafe extern "C" fn ScDetectLdapRegister() { let kw = SCSigTableElmt { @@ -106,4 +237,21 @@ pub unsafe extern "C" fn ScDetectLdapRegister() { false, //to client true, //to server ); + let kw = SCSigTableElmt { + name: b"ldap.responses.operation\0".as_ptr() as *const libc::c_char, + desc: b"match LDAP responses operation\0".as_ptr() as *const libc::c_char, + url: b"/rules/ldap-keywords.html#ldap.responses.operation\0".as_ptr() + as *const libc::c_char, + AppLayerTxMatch: Some(ldap_detect_responses_operation_match), + Setup: ldap_detect_responses_operation_setup, + Free: Some(ldap_detect_responses_free), + flags: 0, + }; + G_LDAP_RESPONSES_OPERATION_KW_ID = DetectHelperKeywordRegister(&kw); + G_LDAP_RESPONSES_OPERATION_BUFFER_ID = DetectHelperBufferRegister( + b"ldap.responses.operation\0".as_ptr() as *const libc::c_char, + ALPROTO_LDAP, + true, //to client + false, //to server + ); }