-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,3 +49,4 @@ Suricata Rules | |
differences-from-snort | ||
multi-buffer-matching | ||
tag | ||
ldap-keywords |
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 | ||
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;) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By default, should be |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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;) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also need a |
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
|
||
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> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function does not need to be |
||
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need the |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
|
||
// written by Giuseppe Longo <[email protected]> | ||
|
||
pub mod detect; | ||
pub mod filters; | ||
pub mod ldap; | ||
pub mod logger; | ||
|
There was a problem hiding this comment.
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 ?