Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

detect: add ldap operation keywords - draft v2 #12343

Closed
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
1 change: 1 addition & 0 deletions doc/userguide/rules/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ Suricata Rules
differences-from-snort
multi-buffer-matching
tag
ldap-keywords
108 changes: 108 additions & 0 deletions doc/userguide/rules/ldap-keywords.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
LDAP Keywords
=============

.. role:: example-rule-action
.. role:: example-rule-header
.. role:: example-rule-options
.. role:: example-rule-emphasis

LDAP Request and Response operations
------------------------------------

.. table:: **Operation values for ldap.request.operation and ldap.response.operation keywords**

==== ================================================
Code Operation
==== ================================================
0 bind_request
1 bind_response
2 unbind_request
3 search_request
4 search_result_entry
5 search_result_done
19 search_result_reference
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should keep the code ordered... @jufajardini ?

6 modify_request
7 modify_response
8 add_request
9 add_response
10 del_request
11 del_response
12 mod_dn_request
13 mod_dn_response
14 compare_request
15 compare_response
16 abandon_request
23 extended_request
24 extended_response
25 intermediate_response
==== ================================================

ldap.request.operation
~~~~~~~~~~~~~~~~~~~~~~

Suricata has a ``ldap.request.operation`` keyword that can be used in signatures to identify
and filter network packets based on Lightweight Directory Access Protocol request operations.

Syntax::

ldap.request.operation: operation;

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

Example
^^^^^^^^

Example of a signature that would alert if the packet has an LDAP bind request operation:

.. container:: example-rule

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

Choose a reason for hiding this comment

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

You should also say somewhere that you can use the strings...



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 <rules-integer-keywords>`.

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
Copy link
Contributor

Choose a reason for hiding this comment

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

By default, should be any, not all

and also use flags such as ``all`` and ``any``.

.. table:: **Index values for ldap.responses.operation keyword**

========= ================================================
Value Description
========= ================================================
[default] Match all indexes
all Match only if all indexes match
any Match all indexes
0>= Match specific index
Copy link
Contributor

Choose a reason for hiding this comment

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

Need also negative indexes

========= ================================================

Examples
^^^^^^^^

Example of a signature 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;)

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;)
Copy link
Contributor

Choose a reason for hiding this comment

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

Also need a ldap.responses.count keyword :-)

244 changes: 244 additions & 0 deletions rust/src/ldap/detect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/* Copyright (C) 2024 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

use super::ldap::{LdapTransaction, ALPROTO_LDAP};
use crate::detect::uint::{
detect_parse_uint_enum, rs_detect_u8_free, rs_detect_u8_match, DetectUintData,
};
use crate::detect::{
DetectHelperBufferRegister, DetectHelperKeywordRegister, DetectSignatureSetAppProto,
SCSigTableElmt, SigMatchAppendSMToList,
};
use crate::ldap::types::{LdapMessage, ProtocolOpCode};

use std::ffi::CStr;
use std::os::raw::{c_int, c_void};
use std::str::FromStr;

pub const DETECT_LDAP_RESP_ANY: i8 = -1;
Copy link
Contributor

Choose a reason for hiding this comment

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

These values -1 and -2 should mean last and before last index...

pub const DETECT_LDAP_RESP_ALL: i8 = -2;

#[derive(Debug, PartialEq)]
pub struct DetectLdapRespData {
pub response: DetectUintData<u8>, //ldap response code
pub index: i8,
Copy link
Contributor

Choose a reason for hiding this comment

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

May we should support indexes bigger than 255==u8::MAX

Let's try i32

Copy link
Contributor

Choose a reason for hiding this comment

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

And if we can do pure rust here, you can have the struct not public, and having the index have a type that is an enum that is either Any, All, or Index(i32)

}

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,
) -> *mut DetectUintData<u8> {
let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
if let Ok(s) = ft_name.to_str() {
if let Some(ctx) = detect_parse_uint_enum::<u8, ProtocolOpCode>(s) {
let boxed = Box::new(ctx);
return Box::into_raw(boxed) as *mut _;
}
}
return std::ptr::null_mut();
}

pub fn aux_ldap_parse_protocol_resp_op(s: &str) -> Option<DetectLdapRespData> {
Copy link
Contributor

Choose a reason for hiding this comment

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

This function does not need to be pub

let parts: Vec<&str> = s.split(',').collect();
if parts.len() > 2 {
return None;
}
let index = if parts.len() == 2 {
if parts[1] == "all" {
DETECT_LDAP_RESP_ALL
} else if parts[1] == "any" {
DETECT_LDAP_RESP_ANY
} else {
let u8_index = i8::from_str(parts[1]).ok()?;
if u8_index < 0 {
return None;
}
u8_index
}
} else {
DETECT_LDAP_RESP_ANY
};
if let Some(ctx) = detect_parse_uint_enum::<u8, ProtocolOpCode>(parts[0]) {
let response = ctx;
return Some(DetectLdapRespData { response, index });
}
return None;
}

unsafe extern "C" fn ldap_parse_protocol_resp_op(
ustr: *const std::os::raw::c_char,
) -> *mut DetectUintData<u8> {
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_request_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_req_op(raw) as *mut c_void;
if ctx.is_null() {
return -1;
}
if SigMatchAppendSMToList(
de,
s,
G_LDAP_REQUEST_OPERATION_KW_ID,
ctx,
G_LDAP_REQUEST_OPERATION_BUFFER_ID,
)
.is_null()
{
ldap_detect_operation_free(std::ptr::null_mut(), ctx);
return -1;
}
return 0;
}

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_operation_free(std::ptr::null_mut(), ctx);
return -1;
}
return 0;
}

unsafe extern "C" fn ldap_detect_request_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, DetectUintData<u8>);
if let Some(request) = &tx.request {
let option: u8 = request.protocol_op.to_u8();
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need the : u8 here ?

return rs_detect_u8_match(option, ctx);
}
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 {
DETECT_LDAP_RESP_ANY => {
for response in &tx.responses {
let option: u8 = response.protocol_op.to_u8();
if rs_detect_u8_match(option, &ctx.response) == 1 {
return 1;
}
}
return 0;
}
DETECT_LDAP_RESP_ALL => {
for response in &tx.responses {
let option: u8 = response.protocol_op.to_u8();
if rs_detect_u8_match(option, &ctx.response) == 0 {
return 0;
}
}
return 1;
}
_ => {
let index: usize = ctx.index as usize;
if tx.responses.len() <= index {
return 0;
}
let response: &LdapMessage = &tx.responses[index];
let option: u8 = response.protocol_op.to_u8();
if rs_detect_u8_match(option, &ctx.response) == 1 {
Copy link
Contributor

Choose a reason for hiding this comment

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

return rs_detect_u8_match(option, &ctx.response) saves 2 lines ;-)

return 1;
}
return 0;
}
}
}

unsafe extern "C" fn ldap_detect_operation_free(_de: *mut c_void, ctx: *mut c_void) {
// Just unbox...
let ctx = cast_pointer!(ctx, DetectUintData<u8>);
rs_detect_u8_free(ctx);
}

#[no_mangle]
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,
url: b"/rules/ldap-keywords.html#ldap.request.operation\0".as_ptr() as *const libc::c_char,
AppLayerTxMatch: Some(ldap_detect_request_operation_match),
Setup: ldap_detect_request_operation_setup,
Free: Some(ldap_detect_operation_free),
flags: 0,
};
G_LDAP_REQUEST_OPERATION_KW_ID = DetectHelperKeywordRegister(&kw);
G_LDAP_REQUEST_OPERATION_BUFFER_ID = DetectHelperBufferRegister(
b"ldap.request.operation\0".as_ptr() as *const libc::c_char,
ALPROTO_LDAP,
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_operation_free),
Copy link
Contributor

Choose a reason for hiding this comment

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

ldap_detect_operation_free frees a DetectUintData<u8>when it should free a DetectLdapRespData

I am afraid you need 2 different free functions

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
);
}
2 changes: 1 addition & 1 deletion rust/src/ldap/ldap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ static LDAP_MAX_TX_DEFAULT: usize = 256;

static mut LDAP_MAX_TX: usize = LDAP_MAX_TX_DEFAULT;

static mut ALPROTO_LDAP: AppProto = ALPROTO_UNKNOWN;
pub(super) static mut ALPROTO_LDAP: AppProto = ALPROTO_UNKNOWN;

const STARTTLS_OID: &str = "1.3.6.1.4.1.1466.20037";

Expand Down
1 change: 1 addition & 0 deletions rust/src/ldap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

// written by Giuseppe Longo <[email protected]>

pub mod detect;
pub mod filters;
pub mod ldap;
pub mod logger;
Expand Down
Loading
Loading