smb/log: configuration option for types logging

suricata.yaml output section for smb now parses a types list
and will restrict logging of transactions to these types.

By default, everything still gets logged

Remove unused rs_smb_log_json_request on the way

Ticket: 7620
pull/12951/head
Philippe Antoine 4 months ago committed by Philippe Antoine
parent 30af626a92
commit 6afad1af51

@ -1149,6 +1149,21 @@ SMB Fields
* "response.native_os" (string): SMB1 native OS string
* "response.native_lm" (string): SMB1 native Lan Manager string
One can restrict which transactions are logged by using the "types" field in the
suricata.yaml file. If this field is not specified, all transactions types are logged.
9 values can be specified with this field as shown below:
Configuration::
- eve-log:
enabled: yes
type: file
filename: eve.json
types:
- smb:
types: [file, tree_connect, negotiate, dcerpc, create,
session_setup, ioctl, rename, set_file_path_info, generic]
Examples of SMB logging:
Pipe open::

@ -18,6 +18,8 @@
use std::str;
use std::string::String;
use uuid;
use suricata_sys::sys::SCConfNode;
use crate::conf::ConfNode;
use crate::jsonbuilder::{JsonBuilder, JsonError};
use crate::smb::smb::*;
use crate::smb::smb1::*;
@ -25,6 +27,8 @@ use crate::smb::smb2::*;
use crate::dcerpc::dcerpc::*;
use crate::smb::funcs::*;
use crate::smb::smb_status::*;
use std::error::Error;
use std::fmt;
#[cfg(not(feature = "debug"))]
fn debug_add_progress(_js: &mut JsonBuilder, _tx: &SMBTransaction) -> Result<(), JsonError> { Ok(()) }
@ -65,8 +69,36 @@ fn guid_to_string(guid: &[u8]) -> String {
}
}
fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransaction) -> Result<(), JsonError>
{
// Wrapping error for either jsonbuilder error or our own custom error if
// tx is not to be logged due to config
#[derive(Debug)]
enum SmbLogError {
SkippedByConf,
Json(JsonError),
}
impl Error for SmbLogError {}
impl fmt::Display for SmbLogError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SmbLogError::SkippedByConf => {
write!(f, "skipped by configuration")
}
SmbLogError::Json(j) => j.fmt(f),
}
}
}
impl From<JsonError> for SmbLogError {
fn from(err: JsonError) -> SmbLogError {
SmbLogError::Json(err)
}
}
fn smb_common_header(
jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransaction, flags: u64,
) -> Result<(), SmbLogError> {
jsb.set_uint("id", tx.id)?;
if state.dialect != 0 {
@ -144,6 +176,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
match tx.type_data {
Some(SMBTransactionTypeData::SESSIONSETUP(ref x)) => {
if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_SESSIONSETUP) == 0 {
return Err(SmbLogError::SkippedByConf);
}
if let Some(ref ntlmssp) = x.ntlmssp {
jsb.open_object("ntlmssp")?;
let domain = String::from_utf8_lossy(&ntlmssp.domain);
@ -191,6 +226,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
}
},
Some(SMBTransactionTypeData::CREATE(ref x)) => {
if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_CREATE) == 0 {
return Err(SmbLogError::SkippedByConf);
}
let mut name_raw = x.filename.to_vec();
name_raw.retain(|&i|i != 0x00);
if !name_raw.is_empty() {
@ -230,6 +268,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
jsb.set_string("fuid", &gs)?;
},
Some(SMBTransactionTypeData::NEGOTIATE(ref x)) => {
if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_NEGOTIATE) == 0 {
return Err(SmbLogError::SkippedByConf);
}
if x.smb_ver == 1 {
jsb.open_array("client_dialects")?;
for d in &x.dialects {
@ -260,6 +301,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
}
},
Some(SMBTransactionTypeData::TREECONNECT(ref x)) => {
if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_TREECONNECT) == 0 {
return Err(SmbLogError::SkippedByConf);
}
let share_name = String::from_utf8_lossy(&x.share_name);
if x.is_pipe {
jsb.set_string("named_pipe", &share_name)?;
@ -292,6 +336,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
}
},
Some(SMBTransactionTypeData::FILE(ref x)) => {
if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_FILE) == 0 {
return Err(SmbLogError::SkippedByConf);
}
let file_name = String::from_utf8_lossy(&x.file_name);
jsb.set_string("filename", &file_name)?;
let share_name = String::from_utf8_lossy(&x.share_name);
@ -300,6 +347,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
jsb.set_string("fuid", &gs)?;
},
Some(SMBTransactionTypeData::RENAME(ref x)) => {
if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_RENAME) == 0 {
return Err(SmbLogError::SkippedByConf);
}
if tx.vercmd.get_version() == 2 {
jsb.open_object("set_info")?;
jsb.set_string("class", "FILE_INFO")?;
@ -317,6 +367,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
jsb.set_string("fuid", &gs)?;
},
Some(SMBTransactionTypeData::DCERPC(ref x)) => {
if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_DCERPC) == 0 {
return Err(SmbLogError::SkippedByConf);
}
jsb.open_object("dcerpc")?;
if x.req_set {
jsb.set_string("request", &dcerpc_type_string(x.req_cmd))?;
@ -400,9 +453,15 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
jsb.close()?;
}
Some(SMBTransactionTypeData::IOCTL(ref x)) => {
if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_IOCTL) == 0 {
return Err(SmbLogError::SkippedByConf);
}
jsb.set_string("function", &fsctl_func_to_string(x.func))?;
},
Some(SMBTransactionTypeData::SETFILEPATHINFO(ref x)) => {
if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_SETFILEPATHINFO) == 0 {
return Err(SmbLogError::SkippedByConf);
}
let mut name_raw = x.filename.to_vec();
name_raw.retain(|&i|i != 0x00);
if !name_raw.is_empty() {
@ -439,14 +498,74 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
let gs = fuid_to_string(&x.fid);
jsb.set_string("fuid", &gs)?;
},
_ => { },
None => {
if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_GENERIC) == 0 {
return Err(SmbLogError::SkippedByConf);
}
},
}
return Ok(());
}
#[no_mangle]
pub extern "C" fn SCSmbLogJsonResponse(jsb: &mut JsonBuilder, state: &mut SMBState, tx: &SMBTransaction) -> bool
{
smb_common_header(jsb, state, tx).is_ok()
pub extern "C" fn SCSmbLogJsonResponse(
jsb: &mut JsonBuilder, state: &mut SMBState, tx: &SMBTransaction, flags: u64,
) -> bool {
smb_common_header(jsb, state, tx, flags).is_ok()
}
// Flag constants for logging types
const SMB_LOG_TYPE_FILE: u64 = BIT_U64!(0);
const SMB_LOG_TYPE_TREECONNECT: u64 = BIT_U64!(1);
const SMB_LOG_TYPE_NEGOTIATE: u64 = BIT_U64!(2);
const SMB_LOG_TYPE_DCERPC: u64 = BIT_U64!(3);
const SMB_LOG_TYPE_CREATE: u64 = BIT_U64!(4);
const SMB_LOG_TYPE_SESSIONSETUP: u64 = BIT_U64!(5);
const SMB_LOG_TYPE_IOCTL: u64 = BIT_U64!(6);
const SMB_LOG_TYPE_RENAME: u64 = BIT_U64!(7);
const SMB_LOG_TYPE_SETFILEPATHINFO: u64 = BIT_U64!(8);
const SMB_LOG_TYPE_GENERIC: u64 = BIT_U64!(9);
const SMB_LOG_DEFAULT_ALL: u64 = 0;
fn get_smb_log_type_from_str(s: &str) -> Option<u64> {
match s {
"file" => Some(SMB_LOG_TYPE_FILE),
"tree_connect" => Some(SMB_LOG_TYPE_TREECONNECT),
"negotiate" => Some(SMB_LOG_TYPE_NEGOTIATE),
"dcerpc" => Some(SMB_LOG_TYPE_DCERPC),
"create" => Some(SMB_LOG_TYPE_CREATE),
"session_setup" => Some(SMB_LOG_TYPE_SESSIONSETUP),
"ioctl" => Some(SMB_LOG_TYPE_IOCTL),
"rename" => Some(SMB_LOG_TYPE_RENAME),
"set_file_path_info" => Some(SMB_LOG_TYPE_SETFILEPATHINFO),
"generic" => Some(SMB_LOG_TYPE_GENERIC),
_ => None,
}
}
#[no_mangle]
pub extern "C" fn SCSmbLogParseConfig(conf: *const SCConfNode) -> u64 {
let conf = ConfNode::wrap(conf);
if let Some(node) = conf.get_child_node("types") {
// iterate smb.types list of types
let mut r = SMB_LOG_DEFAULT_ALL;
let mut node = node.first();
loop {
if node.is_none() {
break;
}
let nodeu = node.unwrap();
if let Some(f) = get_smb_log_type_from_str(nodeu.value()) {
r |= f;
} else {
SCLogWarning!("unknown type for smb logging: {}", nodeu.value());
}
node = nodeu.next();
}
if r == SMB_LOG_DEFAULT_ALL {
SCLogWarning!("empty types list for smb is interpreted as logging all");
}
return r;
}
return SMB_LOG_DEFAULT_ALL;
}

@ -37,29 +37,45 @@ bool EveSMBAddMetadata(const Flow *f, uint64_t tx_id, SCJsonBuilder *jb)
if (state) {
SMBTransaction *tx = AppLayerParserGetTx(f->proto, ALPROTO_SMB, state, tx_id);
if (tx) {
return SCSmbLogJsonResponse(jb, state, tx);
// flags 0 means log all
return SCSmbLogJsonResponse(jb, state, tx, 0);
}
}
return false;
}
typedef struct LogSmbFileCtx_ {
uint64_t flags;
// generic context needed for init by CreateEveThreadCtx
// comes from parent in SMBLogInitSub
OutputJsonCtx *eve_ctx;
} LogSmbFileCtx;
// wrapper structure
typedef struct LogSmbLogThread_ {
// generic structure
OutputJsonThreadCtx *ctx;
// smb-specific structure
LogSmbFileCtx *smblog_ctx;
} LogSmbLogThread;
static int JsonSMBLogger(ThreadVars *tv, void *thread_data,
const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id)
{
OutputJsonThreadCtx *thread = thread_data;
LogSmbLogThread *thread = thread_data;
SCJsonBuilder *jb = CreateEveHeader(p, LOG_DIR_FLOW, "smb", NULL, thread->ctx);
SCJsonBuilder *jb = CreateEveHeader(p, LOG_DIR_FLOW, "smb", NULL, thread->ctx->ctx);
if (unlikely(jb == NULL)) {
return TM_ECODE_FAILED;
}
SCJbOpenObject(jb, "smb");
if (!SCSmbLogJsonResponse(jb, state, tx)) {
if (!SCSmbLogJsonResponse(jb, state, tx, thread->smblog_ctx->flags)) {
goto error;
}
SCJbClose(jb);
OutputJsonBuilderBuffer(tv, p, p->flow, jb, thread);
OutputJsonBuilderBuffer(tv, p, p->flow, jb, thread->ctx);
SCJbFree(jb);
return TM_ECODE_OK;
@ -69,18 +85,73 @@ error:
return TM_ECODE_FAILED;
}
static void LogSmbLogDeInitCtxSub(OutputCtx *output_ctx)
{
LogSmbFileCtx *smblog_ctx = (LogSmbFileCtx *)output_ctx->data;
SCFree(smblog_ctx);
SCFree(output_ctx);
}
static OutputInitResult SMBLogInitSub(SCConfNode *conf, OutputCtx *parent_ctx)
{
AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_SMB);
AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_SMB);
return OutputJsonLogInitSub(conf, parent_ctx);
OutputInitResult r = OutputJsonLogInitSub(conf, parent_ctx);
if (r.ok) {
// generic init is ok, try smb-specific one
LogSmbFileCtx *smblog_ctx = SCCalloc(1, sizeof(LogSmbFileCtx));
if (unlikely(smblog_ctx == NULL)) {
SCFree(r.ctx);
r.ctx = NULL;
r.ok = false;
return r;
}
smblog_ctx->eve_ctx = parent_ctx->data;
// parse config for flags/types to log
smblog_ctx->flags = SCSmbLogParseConfig(conf);
r.ctx->data = smblog_ctx;
r.ctx->DeInit = LogSmbLogDeInitCtxSub;
}
return r;
}
static TmEcode LogSmbLogThreadInit(ThreadVars *t, const void *initdata, void **data)
{
if (initdata == NULL) {
return TM_ECODE_FAILED;
}
LogSmbLogThread *aft = SCCalloc(1, sizeof(LogSmbLogThread));
if (unlikely(aft == NULL)) {
return TM_ECODE_FAILED;
}
aft->smblog_ctx = ((OutputCtx *)initdata)->data;
aft->ctx = CreateEveThreadCtx(t, aft->smblog_ctx->eve_ctx);
if (!aft->ctx) {
SCFree(aft);
return TM_ECODE_FAILED;
}
*data = (void *)aft;
return TM_ECODE_OK;
}
// LogSmbLogThread structure wraps a generic OutputJsonThreadCtx
// created by CreateEveThreadCtx
static TmEcode LogSmbLogThreadDeinit(ThreadVars *t, void *data)
{
LogSmbLogThread *aft = (LogSmbLogThread *)data;
TmEcode r = JsonLogThreadDeinit(t, aft->ctx);
SCFree(aft);
return r;
}
void JsonSMBLogRegister(void)
{
/* Register as an eve sub-module. */
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonSMBLog", "eve-log.smb", SMBLogInitSub,
ALPROTO_SMB, JsonSMBLogger, JsonLogThreadInit, JsonLogThreadDeinit);
ALPROTO_SMB, JsonSMBLogger, LogSmbLogThreadInit, LogSmbLogThreadDeinit);
SCLogDebug("SMB JSON logger registered.");
}

@ -318,7 +318,10 @@ outputs:
- ftp
- rdp
- nfs
- smb
- smb:
# restrict to only certain types in the following list
#types: [file, tree_connect, negotiate, dcerpc, create,
# session_setup, ioctl, rename, set_file_path_info, generic]
- tftp
- ike
- dcerpc

Loading…
Cancel
Save