diff --git a/doc/userguide/rules/ldap-keywords.rst b/doc/userguide/rules/ldap-keywords.rst index d3ea306c0811..9f64e666bfba 100644 --- a/doc/userguide/rules/ldap-keywords.rst +++ b/doc/userguide/rules/ldap-keywords.rst @@ -163,3 +163,83 @@ Example of a signature that would alert if a packet has more than 2 LDAP respons .. container:: example-rule alert ip any any -> any any (msg:"Packet has more than 2 LDAP responses"; :example-rule-emphasis:`ldap.responses.count:>2;` sid:1;) + +ldap.request.dn +--------------- + +Matches on LDAP distinguished names from request operations. + +Comparison is case-sensitive. + +Syntax:: + + ldap.request.dn; content:dc=example,dc=com; + +``ldap.request.dn`` is a 'sticky buffer' and can be used as a ``fast_pattern``. + +This keyword maps to the eve fields: +``ldap.request.bind_request.name`` +``ldap.request.add_request.entry`` +``ldap.request.search_request.base_object`` +``ldap.request.modify_request.object`` +``ldap.request.del_request.dn`` +``ldap.request.mod_dn_request.entry`` +``ldap.request.compare_request.entry`` + +Example +^^^^^^^ + +Example of a signature that would alert if a packet has the LDAP distinguished name ``uid=jdoe,ou=People,dc=example,dc=com``: + +.. container:: example-rule + + alert tcp any any -> any any (msg:"Test LDAPDN"; :example-rule-emphasis:`ldap.request.dn:uid=jdoe,ou=People,dc=example,dc=com;` sid:1;) + +It is possible to use the keyword ``ldap.request.operation`` in the same rule to specify the operation to match. +Here is an example of a signature that would alert if a packet has an LDAP search request operation and contains the LDAP distinguished name ``dc=example,dc=com``. + +.. container:: example-rule + + alert tcp any any -> any any (msg:"Test LDAPDN and operation"; :example-rule-emphasis:`ldap.request.operation:search_request; ldap.request.dn:dc=example,dc=com;` sid:1;) + +ldap.responses.dn +----------------- + +Matches on LDAP distinguished names from response operations. + +Comparison is case-sensitive. + +Syntax:: + + ldap.responses.dn; content:dc=example,dc=com; + +``ldap.responses.dn`` is a 'sticky buffer' and can be used as a ``fast_pattern``. + +``ldap.responses.dn`` supports multiple buffer matching, see :doc:`multi-buffer-matching`. + +This keyword maps to the eve fields: +``ldap.responses[].search_result_entry.base_object`` +``ldap.responses[].bind_response.matched_dn`` +``ldap.responses[].search_result_done.matched_dn`` +``ldap.responses[].modify_response.matched_dn`` +``ldap.responses[].add_response.matched_dn`` +``ldap.responses[].del_response.matched_dn`` +``ldap.responses[].mod_dn_response.matched_dn`` +``ldap.responses[].compare_response.matched_dn`` +``ldap.responses[].extended_response.matched_dn`` + +Example +^^^^^^^ + +Example of a signature that would alert if a packet has the LDAP distinguished name ``dc=example,dc=com``: + +.. container:: example-rule + + alert tcp any any -> any any (msg:"Test LDAPDN"; :example-rule-emphasis:`ldap.responses.dn:dc=example,dc=com;` sid:1;) + +It is possible to use the keyword ``ldap.responses.operation`` in the same rule to specify the operation to match. +Here is an example of a signature that would alert if a packet has an LDAP search result entry operation and contains the LDAP distinguished name ``dc=example,dc=com``. + +.. container:: example-rule + + alert tcp any any -> any any (msg:"Test LDAPDN and operation"; :example-rule-emphasis:`ldap.responses.operation:search_result_entry; ldap.responses.dn:dc=example,dc=com;` sid:1;) \ No newline at end of file diff --git a/rust/src/applayertemplate/detect.rs b/rust/src/applayertemplate/detect.rs index 9eaf880ed785..47f7f040fb83 100644 --- a/rust/src/applayertemplate/detect.rs +++ b/rust/src/applayertemplate/detect.rs @@ -76,7 +76,7 @@ unsafe extern "C" fn template_buffer_get( } #[no_mangle] -pub unsafe extern "C" fn ScDetectTemplateRegister() { +pub unsafe extern "C" fn SCDetectTemplateRegister() { /* TEMPLATE_START_REMOVE */ if conf_get_node("app-layer.protocols.template").is_none() { return; diff --git a/rust/src/enip/detect.rs b/rust/src/enip/detect.rs index c652aa01ca24..0e95fdb36780 100644 --- a/rust/src/enip/detect.rs +++ b/rust/src/enip/detect.rs @@ -1330,7 +1330,7 @@ unsafe extern "C" fn service_name_get_data( ); } #[no_mangle] -pub unsafe extern "C" fn ScDetectEnipRegister() { +pub unsafe extern "C" fn SCDetectEnipRegister() { let kw = SCSigTableElmt { name: b"cip_service\0".as_ptr() as *const libc::c_char, desc: b"match on CIP Service, and optionnally class and attribute\0".as_ptr() diff --git a/rust/src/ldap/detect.rs b/rust/src/ldap/detect.rs index 3745630bfe59..c982be5148e7 100644 --- a/rust/src/ldap/detect.rs +++ b/rust/src/ldap/detect.rs @@ -21,10 +21,12 @@ use crate::detect::uint::{ rs_detect_u8_free, rs_detect_u8_match, DetectUintData, }; use crate::detect::{ - DetectHelperBufferRegister, DetectHelperKeywordRegister, DetectSignatureSetAppProto, - SCSigTableElmt, SigMatchAppendSMToList, + DetectBufferSetActiveList, DetectHelperBufferMpmRegister, DetectHelperBufferRegister, + DetectHelperGetData, DetectHelperGetMultiData, DetectHelperKeywordRegister, + DetectHelperMultiBufferMpmRegister, DetectSignatureSetAppProto, SCSigTableElmt, + SigMatchAppendSMToList, SIGMATCH_INFO_STICKY_BUFFER, SIGMATCH_NOOPT, }; -use crate::ldap::types::{LdapMessage, ProtocolOpCode}; +use crate::ldap::types::{LdapMessage, ProtocolOp, ProtocolOpCode}; use std::ffi::CStr; use std::os::raw::{c_int, c_void}; @@ -53,6 +55,8 @@ static mut G_LDAP_RESPONSES_OPERATION_KW_ID: c_int = 0; static mut G_LDAP_RESPONSES_OPERATION_BUFFER_ID: c_int = 0; 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; unsafe extern "C" fn ldap_parse_protocol_req_op( ustr: *const std::os::raw::c_char, @@ -263,8 +267,120 @@ unsafe extern "C" fn ldap_detect_responses_count_free(_de: *mut c_void, ctx: *mu rs_detect_u32_free(ctx); } +unsafe extern "C" fn ldap_detect_request_dn_setup( + de: *mut c_void, s: *mut c_void, _raw: *const std::os::raw::c_char, +) -> c_int { + if DetectSignatureSetAppProto(s, ALPROTO_LDAP) != 0 { + return -1; + } + if DetectBufferSetActiveList(de, s, G_LDAP_REQUEST_DN_BUFFER_ID) < 0 { + return -1; + } + return 0; +} + +unsafe extern "C" fn ldap_detect_request_dn_get_data( + de: *mut c_void, transforms: *const c_void, flow: *const c_void, flow_flags: u8, + tx: *const c_void, list_id: c_int, +) -> *mut c_void { + return DetectHelperGetData( + de, + transforms, + flow, + flow_flags, + tx, + list_id, + ldap_tx_get_request_dn, + ); +} + +unsafe extern "C" fn ldap_tx_get_request_dn( + tx: *const c_void, _flags: u8, buffer: *mut *const u8, buffer_len: *mut u32, +) -> bool { + let tx = cast_pointer!(tx, LdapTransaction); + + *buffer = std::ptr::null(); + *buffer_len = 0; + + if let Some(request) = &tx.request { + let str_buffer: &str = match &request.protocol_op { + ProtocolOp::BindRequest(req) => req.name.0.as_str(), + ProtocolOp::AddRequest(req) => req.entry.0.as_str(), + ProtocolOp::SearchRequest(req) => req.base_object.0.as_str(), + ProtocolOp::ModifyRequest(req) => req.object.0.as_str(), + ProtocolOp::DelRequest(req) => req.0.as_str(), + ProtocolOp::ModDnRequest(req) => req.entry.0.as_str(), + ProtocolOp::CompareRequest(req) => req.entry.0.as_str(), + _ => return false, + }; + *buffer = str_buffer.as_ptr(); + *buffer_len = str_buffer.len() as u32; + return true; + } + return false; +} + +unsafe extern "C" fn ldap_detect_responses_dn_setup( + de: *mut c_void, s: *mut c_void, _raw: *const std::os::raw::c_char, +) -> c_int { + if DetectSignatureSetAppProto(s, ALPROTO_LDAP) != 0 { + return -1; + } + if DetectBufferSetActiveList(de, s, G_LDAP_RESPONSES_DN_BUFFER_ID) < 0 { + return -1; + } + return 0; +} + +unsafe extern "C" fn ldap_detect_responses_dn_get_data( + de: *mut c_void, transforms: *const c_void, flow: *const c_void, flow_flags: u8, + tx: *const c_void, list_id: c_int, local_id: u32, +) -> *mut c_void { + return DetectHelperGetMultiData( + de, + transforms, + flow, + flow_flags, + tx, + list_id, + local_id, + ldap_tx_get_responses_dn, + ); +} + +unsafe extern "C" fn ldap_tx_get_responses_dn( + tx: *const c_void, _flags: u8, local_id: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> bool { + let tx = cast_pointer!(tx, LdapTransaction); + + if local_id as usize >= tx.responses.len() { + return false; + } + *buffer = std::ptr::null(); + *buffer_len = 0; + + let response = &tx.responses[local_id as usize]; + // We expect every response in one tx to be the same protocol_op + let str_buffer: &str = match &response.protocol_op { + ProtocolOp::SearchResultEntry(req) => req.object_name.0.as_str(), + ProtocolOp::BindResponse(req) => req.result.matched_dn.0.as_str(), + ProtocolOp::SearchResultDone(req) => req.matched_dn.0.as_str(), + ProtocolOp::ModifyResponse(req) => req.result.matched_dn.0.as_str(), + ProtocolOp::AddResponse(req) => req.matched_dn.0.as_str(), + ProtocolOp::DelResponse(req) => req.matched_dn.0.as_str(), + ProtocolOp::ModDnResponse(req) => req.matched_dn.0.as_str(), + ProtocolOp::CompareResponse(req) => req.matched_dn.0.as_str(), + ProtocolOp::ExtendedResponse(req) => req.result.matched_dn.0.as_str(), + _ => return false, + }; + + *buffer = str_buffer.as_ptr(); + *buffer_len = str_buffer.len() as u32; + return true; +} + #[no_mangle] -pub unsafe extern "C" fn ScDetectLdapRegister() { +pub unsafe extern "C" fn SCDetectLdapRegister() { let kw = SCSigTableElmt { name: b"ldap.request.operation\0".as_ptr() as *const libc::c_char, desc: b"match LDAP request operation\0".as_ptr() as *const libc::c_char, @@ -314,4 +430,40 @@ pub unsafe extern "C" fn ScDetectLdapRegister() { true, //to client false, //to server ); + let kw = SCSigTableElmt { + name: b"ldap.request.dn\0".as_ptr() as *const libc::c_char, + desc: b"match request LDAPDN\0".as_ptr() as *const libc::c_char, + url: b"/rules/ldap-keywords.html#ldap.request.dn\0".as_ptr() as *const libc::c_char, + Setup: ldap_detect_request_dn_setup, + flags: SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER, + AppLayerTxMatch: None, + Free: None, + }; + let _g_ldap_request_dn_kw_id = DetectHelperKeywordRegister(&kw); + G_LDAP_REQUEST_DN_BUFFER_ID = DetectHelperBufferMpmRegister( + b"ldap.request.dn\0".as_ptr() as *const libc::c_char, + b"LDAP REQUEST DISTINGUISHED_NAME\0".as_ptr() as *const libc::c_char, + ALPROTO_LDAP, + false, //to client + true, //to server + ldap_detect_request_dn_get_data, + ); + let kw = SCSigTableElmt { + name: b"ldap.responses.dn\0".as_ptr() as *const libc::c_char, + desc: b"match responses LDAPDN\0".as_ptr() as *const libc::c_char, + url: b"/rules/ldap-keywords.html#ldap.responses.dn\0".as_ptr() as *const libc::c_char, + Setup: ldap_detect_responses_dn_setup, + flags: SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER, + AppLayerTxMatch: None, + Free: None, + }; + let _g_ldap_responses_dn_kw_id = DetectHelperKeywordRegister(&kw); + G_LDAP_RESPONSES_DN_BUFFER_ID = DetectHelperMultiBufferMpmRegister( + b"ldap.responses.dn\0".as_ptr() as *const libc::c_char, + b"LDAP RESPONSES DISTINGUISHED_NAME\0".as_ptr() as *const libc::c_char, + ALPROTO_LDAP, + true, //to client + false, //to server + ldap_detect_responses_dn_get_data, + ); } diff --git a/rust/src/mqtt/detect.rs b/rust/src/mqtt/detect.rs index 13ad30285fe9..bbaa3a2c1dd7 100644 --- a/rust/src/mqtt/detect.rs +++ b/rust/src/mqtt/detect.rs @@ -1099,7 +1099,7 @@ unsafe extern "C" fn mqtt_conn_clientid_get_data( } #[no_mangle] -pub unsafe extern "C" fn ScDetectMqttRegister() { +pub unsafe extern "C" fn SCDetectMqttRegister() { let keyword_name = b"mqtt.unsubscribe.topic\0".as_ptr() as *const libc::c_char; let kw = SCSigTableElmt { name: keyword_name, diff --git a/rust/src/rfb/detect.rs b/rust/src/rfb/detect.rs index 81919330cc29..9f724d7cd305 100644 --- a/rust/src/rfb/detect.rs +++ b/rust/src/rfb/detect.rs @@ -187,7 +187,7 @@ unsafe extern "C" fn rfb_sec_result_free(_de: *mut c_void, ctx: *mut c_void) { } #[no_mangle] -pub unsafe extern "C" fn ScDetectRfbRegister() { +pub unsafe extern "C" fn SCDetectRfbRegister() { let kw = SCSigTableElmt { name: b"rfb.name\0".as_ptr() as *const libc::c_char, desc: b"sticky buffer to match on the RFB desktop name\0".as_ptr() as *const libc::c_char, diff --git a/rust/src/sip/detect.rs b/rust/src/sip/detect.rs index ff2ec3e06e13..c8f3b92f10ba 100644 --- a/rust/src/sip/detect.rs +++ b/rust/src/sip/detect.rs @@ -578,7 +578,7 @@ unsafe extern "C" fn sip_content_length_hdr_get_data( return false; } #[no_mangle] -pub unsafe extern "C" fn ScDetectSipRegister() { +pub unsafe extern "C" fn SCDetectSipRegister() { let kw = SCSigTableElmt { name: b"sip.protocol\0".as_ptr() as *const libc::c_char, desc: b"sticky buffer to match on the SIP protocol\0".as_ptr() as *const libc::c_char, diff --git a/rust/src/snmp/detect.rs b/rust/src/snmp/detect.rs index 2f9851bbdff7..0c07538c7fe2 100644 --- a/rust/src/snmp/detect.rs +++ b/rust/src/snmp/detect.rs @@ -183,7 +183,7 @@ pub unsafe extern "C" fn snmp_detect_community_get_data( ); } #[no_mangle] -pub unsafe extern "C" fn ScDetectSNMPRegister() { +pub unsafe extern "C" fn SCDetectSNMPRegister() { let kw = SCSigTableElmt { name: b"snmp.version\0".as_ptr() as *const libc::c_char, desc: b"match SNMP version\0".as_ptr() as *const libc::c_char, diff --git a/rust/src/websocket/detect.rs b/rust/src/websocket/detect.rs index f7f3cc9d46de..2d4614ae5ac0 100644 --- a/rust/src/websocket/detect.rs +++ b/rust/src/websocket/detect.rs @@ -277,7 +277,7 @@ pub unsafe extern "C" fn websocket_detect_payload_get_data( } #[no_mangle] -pub unsafe extern "C" fn ScDetectWebsocketRegister() { +pub unsafe extern "C" fn SCDetectWebsocketRegister() { let kw = SCSigTableElmt { name: b"websocket.opcode\0".as_ptr() as *const libc::c_char, desc: b"match WebSocket opcode\0".as_ptr() as *const libc::c_char, diff --git a/scripts/setup-app-layer.py b/scripts/setup-app-layer.py index f94e68ae7d7e..ca2ecc5b16ac 100755 --- a/scripts/setup-app-layer.py +++ b/scripts/setup-app-layer.py @@ -255,10 +255,10 @@ def detect_patch_detect_engine_register_c(protoname): output = io.StringIO() with open(filename) as infile: for line in infile: - if line.find("ScDetect%sRegister" % protoname) > -1: + if line.find("SCDetect%sRegister" % protoname) > -1: # patch already applied return - if line.find("ScDetectTemplateRegister") > -1: + if line.find("SCDetectTemplateRegister") > -1: new = line.replace("Template", "%s" % protoname) output.write(new) output.write(line) diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 658c17150aec..88b77ec3ce3f 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -734,15 +734,15 @@ void SigTableSetup(void) DetectVlanLayersRegister(); SCDetectSMTPRegister(); - ScDetectSNMPRegister(); + SCDetectSNMPRegister(); SCDetectDHCPRegister(); - ScDetectWebsocketRegister(); - ScDetectEnipRegister(); - ScDetectMqttRegister(); - ScDetectRfbRegister(); - ScDetectSipRegister(); - ScDetectTemplateRegister(); - ScDetectLdapRegister(); + SCDetectWebsocketRegister(); + SCDetectEnipRegister(); + SCDetectMqttRegister(); + SCDetectRfbRegister(); + SCDetectSipRegister(); + SCDetectTemplateRegister(); + SCDetectLdapRegister(); for (size_t i = 0; i < preregistered_callbacks_nb; i++) { PreregisteredCallbacks[i]();