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
1 change: 1 addition & 0 deletions doc/userguide/output/eve/eve-json-output.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ Metadata::

- alert:
#payload: yes # enable dumping payload in Base64
#payload-only-classtypes: [] # dump payload only for specified classtypes (empty = all)
#payload-buffer-size: 4kb # max size of payload buffer to output in eve-log
#payload-printable: yes # enable dumping payload in printable (lossy) format
#payload-length: yes # enable dumping payload length, including the gaps
Expand Down
1 change: 1 addition & 0 deletions doc/userguide/partials/eve-log.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ outputs:
types:
- alert:
# payload: yes # enable dumping payload in Base64
# payload-only-classtypes: [] # dump payload only for specified classtypes (empty = all)
# payload-buffer-size: 4kb # max size of payload buffer to output in eve-log
# payload-printable: yes # enable dumping payload in printable (lossy) format
# payload-length: yes # enable dumping payload length, including the gaps
Expand Down
1 change: 1 addition & 0 deletions src/detect-classtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ static int DetectClasstypeSetup(DetectEngineCtx *de_ctx, Signature *s, const cha
if (real_ct && update_ct) {
s->class_id = ct->classtype_id;
s->class_msg = ct->classtype_desc;
s->classtype = ct->classtype;
}
return 0;
}
Expand Down
2 changes: 2 additions & 0 deletions src/detect.h
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,8 @@ typedef struct Signature_ {

/** classification message */
char *class_msg;
/** classtype */
char *classtype;
/** Reference */
DetectReference *references;
/** Metadata */
Expand Down
138 changes: 137 additions & 1 deletion src/output-json-alert.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,19 @@

#define JSON_STREAM_BUFFER_SIZE 4096

typedef struct ClasstypeFilter_ {
char **classtype_names;
uint32_t count;
} ClasstypeFilter;

typedef struct AlertJsonOutputCtx_ {
LogFileCtx* file_ctx;
uint16_t flags;
uint32_t payload_buffer_size;
HttpXFFCfg *xff_cfg;
HttpXFFCfg *parent_xff_cfg;
OutputJsonCtx *eve_ctx;
ClasstypeFilter *payload_classtype_filter;
} AlertJsonOutputCtx;

typedef struct JsonAlertLogThread_ {
Expand Down Expand Up @@ -640,6 +646,36 @@ static bool AlertJsonStreamData(const AlertJsonOutputCtx *json_output_ctx, JsonA
return false;
}

/**
* \brief Check if alert's classtype matches the payload extraction filter if filtering is configured
* \param filter ClasstypeFilter to check (can be NULL)
* \param pa PacketAlert containing the signature
* \return true if payload should be extracted, false otherwise
*/
static bool ShouldDumpPayloadInAlert(const ClasstypeFilter *filter, const PacketAlert *pa)
{
// No filter configured = always extract based on payload flags (default behavior)
if (filter == NULL) {
return true;
}

// No classtype in alert, yet filtering is configured -> no extraction
if (pa->s == NULL || pa->s->classtype == NULL) {
return false;
}

// Check if classtype in alert matches any in the filter list
const char *alert_classtype = pa->s->classtype;
for (uint32_t i = 0; i < filter->count; i++) {
if (strcasecmp(filter->classtype_names[i], alert_classtype) == 0) {
return true;
}
}

// No match on specified alert classtype
return false;
}

static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p)
{
AlertJsonOutputCtx *json_output_ctx = aft->json_output_ctx;
Expand Down Expand Up @@ -744,8 +780,11 @@ static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p)
}
}

bool should_dump_payload = ShouldDumpPayloadInAlert(
json_output_ctx->payload_classtype_filter, pa);

/* payload */
if (json_output_ctx->flags &
if (should_dump_payload && json_output_ctx->flags &
(LOG_JSON_PAYLOAD | LOG_JSON_PAYLOAD_BASE64 | LOG_JSON_PAYLOAD_LENGTH)) {
int stream = (p->proto == IPPROTO_TCP) ?
(pa->flags & (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_STREAM_MATCH) ?
Expand Down Expand Up @@ -929,6 +968,27 @@ static TmEcode JsonAlertLogThreadDeinit(ThreadVars *t, void *data)
return TM_ECODE_OK;
}

/**
* \brief Free classtype filter structure
*/
static void ClasstypeFilterFree(ClasstypeFilter *filter)
{
if (filter == NULL) {
return;
}

if (filter->classtype_names != NULL) {
for (uint32_t i = 0; i < filter->count; i++) {
if (filter->classtype_names[i] != NULL) {
SCFree(filter->classtype_names[i]);
}
}
SCFree(filter->classtype_names);
}

SCFree(filter);
}

static void JsonAlertLogDeInitCtxSub(OutputCtx *output_ctx)
{
SCLogDebug("cleaning up sub output_ctx %p", output_ctx);
Expand All @@ -940,11 +1000,85 @@ static void JsonAlertLogDeInitCtxSub(OutputCtx *output_ctx)
if (xff_cfg != NULL) {
SCFree(xff_cfg);
}
ClasstypeFilterFree(json_output_ctx->payload_classtype_filter);
SCFree(json_output_ctx);
}
SCFree(output_ctx);
}

/**
* \brief Parse payload-only-classtypes filter from YAML
* \param conf Configuration node
* \return ClasstypeFilter* on success, NULL if not configured or empty
*/
static ClasstypeFilter *JsonAlertParsePayloadClasstypeFilter(SCConfNode *conf)
{
if (conf == NULL) {
return NULL;
}

SCConfNode *payload_extraction_node = SCConfNodeLookupChild(conf, "payload-only-classtypes");
if (payload_extraction_node == NULL) {
return NULL;
}

if (!SCConfNodeIsSequence(payload_extraction_node)) {
SCLogError("payload-only-classtypes must be a list of classtype names");
return NULL;
}

SCConfNode *item;
uint32_t count = 0;
TAILQ_FOREACH(item, &payload_extraction_node->head, next) {
if (item->val != NULL && strlen(item->val) > 0) {
count++;
}
}

if (count == 0) {
SCLogDebug("payload_extraction is empty - treating as not configured");
return NULL;
}

ClasstypeFilter *filter = SCCalloc(1, sizeof(ClasstypeFilter));
if (filter == NULL) {
return NULL;
}

filter->classtype_names = SCCalloc(count, sizeof(char *));
if (filter->classtype_names == NULL) {
SCFree(filter);
return NULL;
}

uint32_t idx = 0;
TAILQ_FOREACH(item, &payload_extraction_node->head, next) {
if (item->val == NULL || strlen(item->val) == 0) {
continue;
}

filter->classtype_names[idx] = SCStrdup(item->val);
if (filter->classtype_names[idx] == NULL) {
for (uint32_t i = 0; i < idx; i++) {
SCFree(filter->classtype_names[i]);
}
SCFree(filter->classtype_names);
SCFree(filter);
return NULL;
}
idx++;
}

filter->count = idx;

SCLogInfo("payload_extraction filter enabled for %u classtype(s)", filter->count);
for (uint32_t i = 0; i < filter->count; i++) {
SCLogDebug(" - %s", filter->classtype_names[i]);
}

return filter;
}

static void SetFlag(const SCConfNode *conf, const char *name, uint16_t flag, uint16_t *out_flags)
{
DEBUG_VALIDATE_BUG_ON(conf == NULL);
Expand Down Expand Up @@ -995,6 +1129,8 @@ static void JsonAlertLogSetupMetadata(AlertJsonOutputCtx *json_output_ctx, SCCon
SetFlag(conf, "verdict", LOG_JSON_VERDICT, &flags);
SetFlag(conf, "payload-length", LOG_JSON_PAYLOAD_LENGTH, &flags);

json_output_ctx->payload_classtype_filter = JsonAlertParsePayloadClasstypeFilter(conf);

/* Check for obsolete flags and warn that they have no effect. */
static const char *deprecated_flags[] = { "http", "tls", "ssh", "smtp", "dnp3", "app-layer",
"flow", NULL };
Expand Down
1 change: 1 addition & 0 deletions suricata.yaml.in
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ outputs:
types:
- alert:
# payload: yes # enable dumping payload in Base64
# payload-only-classtypes: ["list", "of", "classtypes"] # dump payload only on specified classtypes if list not empty
# payload-buffer-size: 4 KiB # max size of payload buffer to output in eve-log
# payload-printable: yes # enable dumping payload in printable (lossy) format
# payload-length: yes # enable dumping payload length, including the gaps
Expand Down