Skip to content

Commit 8e345a2

Browse files
author
Amir Boussejra
committed
Add payload-only-classtypes filtering to suricata conf to filter payload dump by classtype if needed
1 parent c61f1cb commit 8e345a2

File tree

6 files changed

+143
-1
lines changed

6 files changed

+143
-1
lines changed

doc/userguide/output/eve/eve-json-output.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ Metadata::
125125

126126
- alert:
127127
#payload: yes # enable dumping payload in Base64
128+
#payload-only-classtypes: [] # dump payload only for specified classtypes (empty = all)
128129
#payload-buffer-size: 4kb # max size of payload buffer to output in eve-log
129130
#payload-printable: yes # enable dumping payload in printable (lossy) format
130131
#payload-length: yes # enable dumping payload length, including the gaps

doc/userguide/partials/eve-log.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ outputs:
8282
types:
8383
- alert:
8484
# payload: yes # enable dumping payload in Base64
85+
# payload-only-classtypes: [] # dump payload only for specified classtypes (empty = all)
8586
# payload-buffer-size: 4kb # max size of payload buffer to output in eve-log
8687
# payload-printable: yes # enable dumping payload in printable (lossy) format
8788
# payload-length: yes # enable dumping payload length, including the gaps

src/detect-classtype.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ static int DetectClasstypeSetup(DetectEngineCtx *de_ctx, Signature *s, const cha
197197
if (real_ct && update_ct) {
198198
s->class_id = ct->classtype_id;
199199
s->class_msg = ct->classtype_desc;
200+
s->classtype = ct->classtype;
200201
}
201202
return 0;
202203
}

src/detect.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,8 @@ typedef struct Signature_ {
737737

738738
/** classification message */
739739
char *class_msg;
740+
/** classtype */
741+
char *classtype;
740742
/** Reference */
741743
DetectReference *references;
742744
/** Metadata */

src/output-json-alert.c

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,19 @@
9696

9797
#define JSON_STREAM_BUFFER_SIZE 4096
9898

99+
typedef struct ClasstypeFilter_ {
100+
char **classtype_names;
101+
uint32_t count;
102+
} ClasstypeFilter;
103+
99104
typedef struct AlertJsonOutputCtx_ {
100105
LogFileCtx* file_ctx;
101106
uint16_t flags;
102107
uint32_t payload_buffer_size;
103108
HttpXFFCfg *xff_cfg;
104109
HttpXFFCfg *parent_xff_cfg;
105110
OutputJsonCtx *eve_ctx;
111+
ClasstypeFilter *payload_classtype_filter;
106112
} AlertJsonOutputCtx;
107113

108114
typedef struct JsonAlertLogThread_ {
@@ -640,6 +646,36 @@ static bool AlertJsonStreamData(const AlertJsonOutputCtx *json_output_ctx, JsonA
640646
return false;
641647
}
642648

649+
/**
650+
* \brief Check if alert's classtype matches the payload extraction filter if filtering is configured
651+
* \param filter ClasstypeFilter to check (can be NULL)
652+
* \param pa PacketAlert containing the signature
653+
* \return true if payload should be extracted, false otherwise
654+
*/
655+
static bool ShouldDumpPayloadInAlert(const ClasstypeFilter *filter, const PacketAlert *pa)
656+
{
657+
// No filter configured = always extract based on payload flags (default behavior)
658+
if (filter == NULL) {
659+
return true;
660+
}
661+
662+
// No classtype in alert, yet filtering is configured -> no extraction
663+
if (pa->s == NULL || pa->s->classtype == NULL) {
664+
return false;
665+
}
666+
667+
// Check if classtype in alert matches any in the filter list
668+
const char *alert_classtype = pa->s->classtype;
669+
for (uint32_t i = 0; i < filter->count; i++) {
670+
if (strcasecmp(filter->classtype_names[i], alert_classtype) == 0) {
671+
return true;
672+
}
673+
}
674+
675+
// No match on specified alert classtype
676+
return false;
677+
}
678+
643679
static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p)
644680
{
645681
AlertJsonOutputCtx *json_output_ctx = aft->json_output_ctx;
@@ -744,8 +780,11 @@ static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p)
744780
}
745781
}
746782

783+
bool should_dump_payload = ShouldDumpPayloadInAlert(
784+
json_output_ctx->payload_classtype_filter, pa);
785+
747786
/* payload */
748-
if (json_output_ctx->flags &
787+
if (should_dump_payload && json_output_ctx->flags &
749788
(LOG_JSON_PAYLOAD | LOG_JSON_PAYLOAD_BASE64 | LOG_JSON_PAYLOAD_LENGTH)) {
750789
int stream = (p->proto == IPPROTO_TCP) ?
751790
(pa->flags & (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_STREAM_MATCH) ?
@@ -929,6 +968,27 @@ static TmEcode JsonAlertLogThreadDeinit(ThreadVars *t, void *data)
929968
return TM_ECODE_OK;
930969
}
931970

971+
/**
972+
* \brief Free classtype filter structure
973+
*/
974+
static void ClasstypeFilterFree(ClasstypeFilter *filter)
975+
{
976+
if (filter == NULL) {
977+
return;
978+
}
979+
980+
if (filter->classtype_names != NULL) {
981+
for (uint32_t i = 0; i < filter->count; i++) {
982+
if (filter->classtype_names[i] != NULL) {
983+
SCFree(filter->classtype_names[i]);
984+
}
985+
}
986+
SCFree(filter->classtype_names);
987+
}
988+
989+
SCFree(filter);
990+
}
991+
932992
static void JsonAlertLogDeInitCtxSub(OutputCtx *output_ctx)
933993
{
934994
SCLogDebug("cleaning up sub output_ctx %p", output_ctx);
@@ -940,11 +1000,85 @@ static void JsonAlertLogDeInitCtxSub(OutputCtx *output_ctx)
9401000
if (xff_cfg != NULL) {
9411001
SCFree(xff_cfg);
9421002
}
1003+
ClasstypeFilterFree(json_output_ctx->payload_classtype_filter);
9431004
SCFree(json_output_ctx);
9441005
}
9451006
SCFree(output_ctx);
9461007
}
9471008

1009+
/**
1010+
* \brief Parse payload-only-classtypes filter from YAML
1011+
* \param conf Configuration node
1012+
* \return ClasstypeFilter* on success, NULL if not configured or empty
1013+
*/
1014+
static ClasstypeFilter *JsonAlertParsePayloadClasstypeFilter(SCConfNode *conf)
1015+
{
1016+
if (conf == NULL) {
1017+
return NULL;
1018+
}
1019+
1020+
SCConfNode *payload_extraction_node = SCConfNodeLookupChild(conf, "payload-only-classtypes");
1021+
if (payload_extraction_node == NULL) {
1022+
return NULL;
1023+
}
1024+
1025+
if (!SCConfNodeIsSequence(payload_extraction_node)) {
1026+
SCLogError("payload-only-classtypes must be a list of classtype names");
1027+
return NULL;
1028+
}
1029+
1030+
SCConfNode *item;
1031+
uint32_t count = 0;
1032+
TAILQ_FOREACH(item, &payload_extraction_node->head, next) {
1033+
if (item->val != NULL && strlen(item->val) > 0) {
1034+
count++;
1035+
}
1036+
}
1037+
1038+
if (count == 0) {
1039+
SCLogDebug("payload_extraction is empty - treating as not configured");
1040+
return NULL;
1041+
}
1042+
1043+
ClasstypeFilter *filter = SCCalloc(1, sizeof(ClasstypeFilter));
1044+
if (filter == NULL) {
1045+
return NULL;
1046+
}
1047+
1048+
filter->classtype_names = SCCalloc(count, sizeof(char *));
1049+
if (filter->classtype_names == NULL) {
1050+
SCFree(filter);
1051+
return NULL;
1052+
}
1053+
1054+
uint32_t idx = 0;
1055+
TAILQ_FOREACH(item, &payload_extraction_node->head, next) {
1056+
if (item->val == NULL || strlen(item->val) == 0) {
1057+
continue;
1058+
}
1059+
1060+
filter->classtype_names[idx] = SCStrdup(item->val);
1061+
if (filter->classtype_names[idx] == NULL) {
1062+
for (uint32_t i = 0; i < idx; i++) {
1063+
SCFree(filter->classtype_names[i]);
1064+
}
1065+
SCFree(filter->classtype_names);
1066+
SCFree(filter);
1067+
return NULL;
1068+
}
1069+
idx++;
1070+
}
1071+
1072+
filter->count = idx;
1073+
1074+
SCLogInfo("payload_extraction filter enabled for %u classtype(s)", filter->count);
1075+
for (uint32_t i = 0; i < filter->count; i++) {
1076+
SCLogDebug(" - %s", filter->classtype_names[i]);
1077+
}
1078+
1079+
return filter;
1080+
}
1081+
9481082
static void SetFlag(const SCConfNode *conf, const char *name, uint16_t flag, uint16_t *out_flags)
9491083
{
9501084
DEBUG_VALIDATE_BUG_ON(conf == NULL);
@@ -995,6 +1129,8 @@ static void JsonAlertLogSetupMetadata(AlertJsonOutputCtx *json_output_ctx, SCCon
9951129
SetFlag(conf, "verdict", LOG_JSON_VERDICT, &flags);
9961130
SetFlag(conf, "payload-length", LOG_JSON_PAYLOAD_LENGTH, &flags);
9971131

1132+
json_output_ctx->payload_classtype_filter = JsonAlertParsePayloadClasstypeFilter(conf);
1133+
9981134
/* Check for obsolete flags and warn that they have no effect. */
9991135
static const char *deprecated_flags[] = { "http", "tls", "ssh", "smtp", "dnp3", "app-layer",
10001136
"flow", NULL };

suricata.yaml.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ outputs:
179179
types:
180180
- alert:
181181
# payload: yes # enable dumping payload in Base64
182+
# payload-only-classtypes: ["list", "of", "classtypes"] # dump payload only on specified classtypes if list not empty
182183
# payload-buffer-size: 4 KiB # max size of payload buffer to output in eve-log
183184
# payload-printable: yes # enable dumping payload in printable (lossy) format
184185
# payload-length: yes # enable dumping payload length, including the gaps

0 commit comments

Comments
 (0)