Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 100 additions & 12 deletions doc/userguide/rules/ldap-keywords.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Syntax::

ldap.request.operation uses :ref:`unsigned 8-bit integer <rules-integer-keywords>`.

This keyword maps to the eve field ``ldap.request.operation``
This keyword maps to the EVE field ``ldap.request.operation``

Examples
^^^^^^^^
Expand All @@ -61,11 +61,11 @@ 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:0;` sid:1;)
alert ldap any any -> any any (msg:"Test LDAP bind request"; :example-rule-emphasis:`ldap.request.operation:0;` sid:1;)

.. container:: example-rule

alert tcp any any -> any any (msg:"Test LDAP bind request"; :example-rule-emphasis:`ldap.request.operation:bind_request;` sid:1;)
alert ldap any any -> any any (msg:"Test LDAP bind request"; :example-rule-emphasis:`ldap.request.operation:bind_request;` sid:1;)

ldap.responses.operation
------------------------
Expand All @@ -79,7 +79,7 @@ Syntax::

ldap.responses.operation uses :ref:`unsigned 8-bit integer <rules-integer-keywords>`.

This keyword maps to the eve field ``ldap.responses[].operation``
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
Expand All @@ -104,31 +104,31 @@ Example of a signatures that would alert if the packet has an LDAP bind response

.. container:: example-rule

alert tcp any any -> any any (msg:"Test LDAP bind response"; :example-rule-emphasis:`ldap.responses.operation:1;` sid:1;)
alert ldap 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;)
alert ldap 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;)
alert ldap 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;)
alert ldap 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;)
alert ldap any any -> any any (msg:"Test LDAP search response"; :example-rule-emphasis:`ldap.responses.operation:search_result_entry,-1;` sid:1;)

ldap.responses.count
--------------------
Expand All @@ -147,7 +147,7 @@ It can be matched exactly, or compared using the ``op`` setting::

ldap.responses.count uses :ref:`unsigned 32-bit integer <rules-integer-keywords>`.

This keyword maps to the eve field ``len(ldap.responses[])``
This keyword maps to the EVE field ``len(ldap.responses[])``

Examples
^^^^^^^^
Expand All @@ -156,10 +156,98 @@ Example of a signature that would alert if a packet has 0 LDAP responses:

.. container:: example-rule

alert ip any any -> any any (msg:"Packet has 0 LDAP responses"; :example-rule-emphasis:`ldap.responses.count:0;` sid:1;)
alert ldap any any -> any any (msg:"Packet has 0 LDAP responses"; :example-rule-emphasis:`ldap.responses.count:0;` sid:1;)

Example of a signature that would alert if a packet has more than 2 LDAP responses:

.. 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;)
alert ldap 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 ldap any any -> any any (msg:"Test LDAPDN"; :example-rule-emphasis:`ldap.request.dn; content:"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 ldap any any -> any any (msg:"Test LDAPDN and operation"; :example-rule-emphasis:`ldap.request.operation:search_request; ldap.request.dn; content:"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 ldap any any -> any any (msg:"Test LDAPDN"; :example-rule-emphasis:`ldap.responses.dn; content:"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 at index 1 on the responses array,
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;)
2 changes: 1 addition & 1 deletion rust/src/applayertemplate/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion rust/src/enip/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
160 changes: 156 additions & 4 deletions rust/src/ldap/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about IntermediateResponse and SearchResultReference cases ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Intermediate Response, we should find real pcaps with it to see how it happens

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we also need a pcap with SearchResultReference

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(For me, the PR is good, but I would like to better test/investigate/understand these cases)

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,
Expand Down Expand Up @@ -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,
);
}
2 changes: 1 addition & 1 deletion rust/src/mqtt/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading