mirror of https://github.com/OISF/suricata
Merge 5b1f50ef0d into 9a37bb659b
commit
6079963f55
@ -0,0 +1,79 @@
|
||||
IMAP Keywords
|
||||
=============
|
||||
|
||||
.. role:: example-rule-action
|
||||
.. role:: example-rule-header
|
||||
.. role:: example-rule-options
|
||||
.. role:: example-rule-emphasis
|
||||
|
||||
imap.request
|
||||
------------
|
||||
|
||||
Matches on IMAP request lines sent from the client to the server.
|
||||
|
||||
Each buffer is a request line as sent by the client, without the trailing
|
||||
CRLF. Literal data is not part of the buffer. Unlike the EVE
|
||||
``imap.requests`` field, ``LOGIN`` and ``AUTHENTICATE`` arguments are not
|
||||
redacted.
|
||||
|
||||
Syntax::
|
||||
|
||||
imap.request; content:"<content to match against>";
|
||||
|
||||
``imap.request`` is a 'sticky buffer' and can be used as a ``fast_pattern``.
|
||||
|
||||
``imap.request`` supports multiple buffer matching, see :doc:`multi-buffer-matching`.
|
||||
|
||||
This keyword maps to the EVE field ``imap.requests[]``
|
||||
|
||||
Examples
|
||||
^^^^^^^^
|
||||
|
||||
Example of a signature that would alert if an IMAP request contains a LOGIN command:
|
||||
|
||||
.. container:: example-rule
|
||||
|
||||
alert imap any any -> any any (msg:"IMAP LOGIN request"; :example-rule-emphasis:`imap.request; content:"LOGIN";` sid:1;)
|
||||
|
||||
imap.response
|
||||
-------------
|
||||
|
||||
Matches on IMAP response lines sent from the server to the client.
|
||||
|
||||
Syntax::
|
||||
|
||||
imap.response; content:"<content to match against>";
|
||||
|
||||
``imap.response`` is a 'sticky buffer' and can be used as a ``fast_pattern``.
|
||||
|
||||
``imap.response`` supports multiple buffer matching, see :doc:`multi-buffer-matching`.
|
||||
|
||||
This keyword maps to the EVE field ``imap.responses[]``
|
||||
|
||||
Examples
|
||||
^^^^^^^^
|
||||
|
||||
Example of a signature that would alert if an IMAP response contains "OK":
|
||||
|
||||
.. container:: example-rule
|
||||
|
||||
alert imap any any -> any any (msg:"IMAP OK response"; :example-rule-emphasis:`imap.response; content:"OK";` sid:1;)
|
||||
|
||||
Frames
|
||||
------
|
||||
|
||||
The IMAP parser supports the following frames:
|
||||
|
||||
* imap.pdu
|
||||
|
||||
imap.pdu
|
||||
^^^^^^^^
|
||||
|
||||
A single IMAP request or response PDU. Each command from the client or
|
||||
response line from the server creates a separate frame.
|
||||
|
||||
.. container:: example-rule
|
||||
|
||||
alert imap any any -> any any (\
|
||||
:example-rule-options:`frame:imap.pdu; content:"LOGIN"; startswith;` \
|
||||
sid:1;)
|
||||
@ -0,0 +1,8 @@
|
||||
# IMAP app layer event rules
|
||||
#
|
||||
# SID's fall in the 2244000+ range. See rules/README.md
|
||||
|
||||
alert imap any any -> any any (msg:"SURICATA IMAP too many transactions"; app-layer-event:imap.too_many_transactions; classtype:protocol-command-decode; sid:2244000; rev:1;)
|
||||
alert imap any any -> any any (msg:"SURICATA IMAP invalid data"; app-layer-event:imap.invalid_data; classtype:protocol-command-decode; sid:2244001; rev:1;)
|
||||
alert imap any any -> any any (msg:"SURICATA IMAP line too long"; app-layer-event:imap.line_too_long; classtype:protocol-command-decode; sid:2244002; rev:1;)
|
||||
alert imap any any -> any any (msg:"SURICATA IMAP data limit reached"; app-layer-event:imap.data_limit_reached; classtype:protocol-command-decode; sid:2244003; rev:1;)
|
||||
@ -0,0 +1,153 @@
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
// Author: Giuseppe Longo <glongo@oisf.net>
|
||||
|
||||
use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER};
|
||||
use crate::detect::{
|
||||
SIGMATCH_INFO_MULTI_BUFFER, SIGMATCH_INFO_STICKY_BUFFER, SIGMATCH_NOOPT,
|
||||
SIGMATCH_SUPPORT_FIREWALL,
|
||||
};
|
||||
use crate::imap::imap::{ImapTransaction, ALPROTO_IMAP};
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
use suricata_sys::sys::{
|
||||
DetectEngineCtx, DetectEngineThreadCtx, SCDetectBufferSetActiveList,
|
||||
SCDetectHelperKeywordRegister, SCDetectHelperKeywordSetCleanCString,
|
||||
SCDetectHelperMultiBufferProgressMpmRegister, SCDetectSignatureSetAppProto,
|
||||
SCSigTableAppLiteElmt, Signature,
|
||||
};
|
||||
|
||||
static mut G_IMAP_REQUEST_BUFFER_ID: c_int = 0;
|
||||
static mut G_IMAP_RESPONSE_BUFFER_ID: c_int = 0;
|
||||
|
||||
unsafe extern "C" fn imap_detect_request_setup(
|
||||
de: *mut DetectEngineCtx, s: *mut Signature, _raw: *const std::os::raw::c_char,
|
||||
) -> c_int {
|
||||
if SCDetectSignatureSetAppProto(s, ALPROTO_IMAP) != 0 {
|
||||
return -1;
|
||||
}
|
||||
if SCDetectBufferSetActiveList(de, s, G_IMAP_REQUEST_BUFFER_ID) < 0 {
|
||||
return -1;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
unsafe extern "C" fn imap_tx_get_request(
|
||||
_de: *mut DetectEngineThreadCtx, tx: *const c_void, _flags: u8, local_id: u32,
|
||||
buffer: *mut *const u8, buffer_len: *mut u32,
|
||||
) -> bool {
|
||||
let tx = cast_pointer!(tx, ImapTransaction);
|
||||
|
||||
let Some(request) = tx.requests.get(local_id as usize) else {
|
||||
return false;
|
||||
};
|
||||
*buffer = request.raw_line.as_ptr();
|
||||
*buffer_len = request.raw_line.len() as u32;
|
||||
true
|
||||
}
|
||||
|
||||
unsafe extern "C" fn imap_detect_response_setup(
|
||||
de: *mut DetectEngineCtx, s: *mut Signature, _raw: *const std::os::raw::c_char,
|
||||
) -> c_int {
|
||||
if SCDetectSignatureSetAppProto(s, ALPROTO_IMAP) != 0 {
|
||||
return -1;
|
||||
}
|
||||
if SCDetectBufferSetActiveList(de, s, G_IMAP_RESPONSE_BUFFER_ID) < 0 {
|
||||
return -1;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
unsafe extern "C" fn imap_tx_get_response(
|
||||
_de: *mut DetectEngineThreadCtx, tx: *const c_void, _flags: u8, local_id: u32,
|
||||
buffer: *mut *const u8, buffer_len: *mut u32,
|
||||
) -> bool {
|
||||
let tx = cast_pointer!(tx, ImapTransaction);
|
||||
|
||||
let Some(line) = tx.response_lines.get(local_id as usize) else {
|
||||
return false;
|
||||
};
|
||||
*buffer = line.as_ptr();
|
||||
*buffer_len = line.len() as u32;
|
||||
true
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn SCDetectImapRegister() {
|
||||
let name = CString::new("imap.request").unwrap().into_raw();
|
||||
let desc = CString::new("match on IMAP request line")
|
||||
.unwrap()
|
||||
.into_raw();
|
||||
let url = CString::new("/rules/imap-keywords.html#imap-request")
|
||||
.unwrap()
|
||||
.into_raw();
|
||||
let kw = SCSigTableAppLiteElmt {
|
||||
name,
|
||||
desc,
|
||||
url,
|
||||
Setup: Some(imap_detect_request_setup),
|
||||
flags: SIGMATCH_NOOPT
|
||||
| SIGMATCH_INFO_STICKY_BUFFER
|
||||
| SIGMATCH_INFO_MULTI_BUFFER
|
||||
| SIGMATCH_SUPPORT_FIREWALL,
|
||||
AppLayerTxMatch: None,
|
||||
Free: None,
|
||||
};
|
||||
let r = SCDetectHelperKeywordRegister(&kw);
|
||||
SCDetectHelperKeywordSetCleanCString(r);
|
||||
|
||||
G_IMAP_REQUEST_BUFFER_ID = SCDetectHelperMultiBufferProgressMpmRegister(
|
||||
b"imap.request\0".as_ptr() as *const libc::c_char,
|
||||
b"IMAP REQUEST LINE\0".as_ptr() as *const libc::c_char,
|
||||
ALPROTO_IMAP,
|
||||
STREAM_TOSERVER,
|
||||
Some(imap_tx_get_request),
|
||||
0,
|
||||
);
|
||||
|
||||
let name = CString::new("imap.response").unwrap().into_raw();
|
||||
let desc = CString::new("match on IMAP response line")
|
||||
.unwrap()
|
||||
.into_raw();
|
||||
let url = CString::new("/rules/imap-keywords.html#imap-response")
|
||||
.unwrap()
|
||||
.into_raw();
|
||||
let kw = SCSigTableAppLiteElmt {
|
||||
name,
|
||||
desc,
|
||||
url,
|
||||
Setup: Some(imap_detect_response_setup),
|
||||
flags: SIGMATCH_NOOPT
|
||||
| SIGMATCH_INFO_STICKY_BUFFER
|
||||
| SIGMATCH_INFO_MULTI_BUFFER
|
||||
| SIGMATCH_SUPPORT_FIREWALL,
|
||||
AppLayerTxMatch: None,
|
||||
Free: None,
|
||||
};
|
||||
let r = SCDetectHelperKeywordRegister(&kw);
|
||||
SCDetectHelperKeywordSetCleanCString(r);
|
||||
|
||||
G_IMAP_RESPONSE_BUFFER_ID = SCDetectHelperMultiBufferProgressMpmRegister(
|
||||
b"imap.response\0".as_ptr() as *const libc::c_char,
|
||||
b"IMAP RESPONSE LINE\0".as_ptr() as *const libc::c_char,
|
||||
ALPROTO_IMAP,
|
||||
STREAM_TOCLIENT,
|
||||
Some(imap_tx_get_response),
|
||||
1,
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,97 @@
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
// Author: Giuseppe Longo <glongo@oisf.net>
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::imap::imap::ImapTransaction;
|
||||
use crate::imap::parser::{ImapCommand, ImapMessage, ImapMessageType};
|
||||
use crate::jsonbuilder::{JsonBuilder, JsonError};
|
||||
|
||||
const REDACTED: &str = "<redacted>";
|
||||
|
||||
fn log_request_line(
|
||||
request: &ImapMessage, suppress_continuation_data: bool,
|
||||
) -> Option<Cow<'_, str>> {
|
||||
if request.raw_line.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match &request.message {
|
||||
ImapMessageType::ContinuationData if suppress_continuation_data => None,
|
||||
ImapMessageType::Command {
|
||||
command: ImapCommand::Login,
|
||||
arguments,
|
||||
..
|
||||
} if !arguments.is_empty() => {
|
||||
let tag = request.tag_str();
|
||||
Some(Cow::Owned(format!("{tag} LOGIN {REDACTED}")))
|
||||
}
|
||||
ImapMessageType::Command {
|
||||
command: ImapCommand::Authenticate,
|
||||
arguments,
|
||||
..
|
||||
} if arguments.len() > 1 => {
|
||||
let tag = request.tag_str();
|
||||
let mechanism = String::from_utf8_lossy(&arguments[0]);
|
||||
Some(Cow::Owned(format!(
|
||||
"{tag} AUTHENTICATE {mechanism} {REDACTED}"
|
||||
)))
|
||||
}
|
||||
_ => Some(String::from_utf8_lossy(&request.raw_line)),
|
||||
}
|
||||
}
|
||||
|
||||
fn log_imap(tx: &ImapTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> {
|
||||
js.open_object("imap")?;
|
||||
|
||||
if !tx.requests.is_empty() {
|
||||
js.open_array("requests")?;
|
||||
|
||||
let suppress_continuation_data = tx.command.eq_ignore_ascii_case(b"AUTHENTICATE")
|
||||
|| tx.command.eq_ignore_ascii_case(b"LOGIN");
|
||||
for req in &tx.requests {
|
||||
if let Some(line) = log_request_line(req, suppress_continuation_data) {
|
||||
js.append_string(&line)?;
|
||||
}
|
||||
}
|
||||
|
||||
js.close()?;
|
||||
}
|
||||
|
||||
if !tx.response_lines.is_empty() {
|
||||
js.open_array("responses")?;
|
||||
|
||||
for line in &tx.response_lines {
|
||||
js.append_string(&String::from_utf8_lossy(line))?;
|
||||
}
|
||||
js.close()?;
|
||||
}
|
||||
|
||||
js.close()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn SCImapLoggerLog(
|
||||
tx: *mut std::os::raw::c_void, js: &mut JsonBuilder,
|
||||
) -> bool {
|
||||
let tx = cast_pointer!(tx, ImapTransaction);
|
||||
log_imap(tx, js).is_ok()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,133 +0,0 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
*
|
||||
* \author Mahmoud Maatuq <mahmoudmatook.mm@gmail.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "suricata-common.h"
|
||||
#include "app-layer-detect-proto.h"
|
||||
#include "app-layer-imap.h"
|
||||
#include "app-layer-protos.h"
|
||||
#include "rust.h"
|
||||
#include "stream-tcp-private.h"
|
||||
#include "stream-tcp-reassemble.h"
|
||||
#include "util-debug.h"
|
||||
|
||||
static AppProto ImapClientProbingParser(
|
||||
const Flow *f, uint8_t direction, const uint8_t *input, uint32_t len, uint8_t *rdir)
|
||||
{
|
||||
// This is called after " CAPABILITY" pattern has been found
|
||||
if (f->alproto_tc == ALPROTO_IMAP) {
|
||||
// server was already recognised as IMAP, so ok
|
||||
return ALPROTO_IMAP;
|
||||
}
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (input[i] >= '0' && input[i] <= '9') {
|
||||
// We check if the tag at the beginning of the input contains a number
|
||||
return ALPROTO_IMAP;
|
||||
}
|
||||
if (input[i] == ' ') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TcpSession *ssn = (TcpSession *)f->protoctx;
|
||||
if (ssn == NULL) {
|
||||
return ALPROTO_FAILED;
|
||||
}
|
||||
const uint32_t size_tc = StreamDataAvailableForProtoDetect(&ssn->server);
|
||||
|
||||
if (size_tc < 8 && f->alproto_tc == ALPROTO_UNKNOWN) {
|
||||
// wait for another packet from server
|
||||
return ALPROTO_UNKNOWN;
|
||||
}
|
||||
return ALPROTO_FAILED;
|
||||
}
|
||||
|
||||
static int IMAPRegisterPatternsForProtocolDetection(void)
|
||||
{
|
||||
if (SCAppLayerProtoDetectPMRegisterPatternCI(
|
||||
IPPROTO_TCP, ALPROTO_IMAP, "* OK ", 5, 0, STREAM_TOCLIENT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SCAppLayerProtoDetectPMRegisterPatternCI(
|
||||
IPPROTO_TCP, ALPROTO_IMAP, "* NO ", 5, 0, STREAM_TOCLIENT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SCAppLayerProtoDetectPMRegisterPatternCI(
|
||||
IPPROTO_TCP, ALPROTO_IMAP, "* BAD ", 6, 0, STREAM_TOCLIENT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SCAppLayerProtoDetectPMRegisterPatternCI(
|
||||
IPPROTO_TCP, ALPROTO_IMAP, "* LIST ", 7, 0, STREAM_TOCLIENT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SCAppLayerProtoDetectPMRegisterPatternCI(
|
||||
IPPROTO_TCP, ALPROTO_IMAP, "* ESEARCH ", 10, 0, STREAM_TOCLIENT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SCAppLayerProtoDetectPMRegisterPatternCI(
|
||||
IPPROTO_TCP, ALPROTO_IMAP, "* STATUS ", 9, 0, STREAM_TOCLIENT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SCAppLayerProtoDetectPMRegisterPatternCI(
|
||||
IPPROTO_TCP, ALPROTO_IMAP, "* FLAGS ", 8, 0, STREAM_TOCLIENT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* there is no official document that limits the length of the tag
|
||||
* some practical implementations limit it to 20 characters
|
||||
* but keeping depth equal to 31 fails unit tests such AppLayerTest10
|
||||
* so keeping depth 17 for now to pass unit tests, that might miss some detections
|
||||
* until we find a better solution for the unit tests.
|
||||
*
|
||||
* AppLayerTest10 fails because it expects protocol detection to be completed with only 17 bytes
|
||||
* as input, and with this new pattern, we would need more bytes to finish protocol detection.
|
||||
*/
|
||||
if (SCAppLayerProtoDetectPMRegisterPatternCIwPP(IPPROTO_TCP, ALPROTO_IMAP, " CAPABILITY",
|
||||
17 /*6 for max tag len + space + len(CAPABILITY)*/, 0, STREAM_TOSERVER,
|
||||
ImapClientProbingParser, 12, 17) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RegisterIMAPParsers(void)
|
||||
{
|
||||
const char *proto_name = "imap";
|
||||
|
||||
if (SCAppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
|
||||
SCLogDebug("IMAP protocol detection is enabled.");
|
||||
AppLayerProtoDetectRegisterProtocol(ALPROTO_IMAP, proto_name);
|
||||
if (IMAPRegisterPatternsForProtocolDetection() < 0)
|
||||
SCLogError("Failed to register IMAP protocol detection patterns.");
|
||||
} else {
|
||||
SCLogDebug("Protocol detector and parser disabled for IMAP.");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue