detect/firewall: support alert in default app policy

Support `alert` as a secondary action in app-layer firewall policies.

To implement this a Signature object is created per policy that uses
alert, and this is stored in a hash table. When the policy is applied
the signature is looked up and used in the PacketAlert.

Ticket: #8566.
pull/15475/head
Victor Julien 4 months ago
parent 6e037d9783
commit 2d4f1968b8

@ -2014,6 +2014,16 @@
}
}
},
"firewall": {
"type": "object",
"additionalProperties": false,
"properties": {
"hook": {
"type": "string",
"description": "Firewall hook for the match"
}
}
},
"flow": {
"type": "object",
"additionalProperties": false,

@ -10,6 +10,7 @@ signature IDs.
| Component | Start | End |
| ----------------- | ------- | ------- |
| Decoder | 2200000 | 2200999 |
| Firewall | 2201000 | 2201999 |
| Stream | 2210000 | 2210999 |
| Generic App-Layer | 2260000 | 2260999 |

@ -2103,25 +2103,6 @@ void EngineAnalysisRules(const DetectEngineCtx *de_ctx,
#include "app-layer-parser.h"
static const char *ActionScopeToString(enum ActionScope s)
{
switch (s) {
case ACTION_SCOPE_PACKET:
return "packet";
case ACTION_SCOPE_FLOW:
return "flow";
break;
case ACTION_SCOPE_HOOK:
return "hook";
case ACTION_SCOPE_TX:
return "tx";
case ACTION_SCOPE_AUTO:
return "auto";
}
DEBUG_VALIDATE_BUG_ON(1);
return "unknown";
}
static void AddPolicy(const DetectEngineCtx *de_ctx, RuleAnalyzer *ctx, const AppProto a,
const uint8_t state, const uint8_t direction)
{

@ -2801,6 +2801,9 @@ void DetectEngineCtxFree(DetectEngineCtx *de_ctx)
if (de_ctx->non_pf_engine_names) {
HashTableFree(de_ctx->non_pf_engine_names);
}
if (de_ctx->fw_policies) {
HashTableFree(de_ctx->fw_policies->policy_signatures);
}
SCFree(de_ctx->fw_policies);
SCFree(de_ctx);
//DetectAddressGroupPrintMemory();

@ -3748,6 +3748,122 @@ void DetectSetupParseRegexes(const char *parse_str, DetectParseRegex *detect_par
}
}
static uint32_t PolicySignatureHashFunc(HashTable *ht, void *data, uint16_t datalen)
{
const Signature *s = data;
const int dir = 1 + (s->flags & SIG_FLAG_TOSERVER) != 0; // 2 for ts, 1 for tc
uint32_t hash = s->alproto * s->app_progress_hook * dir;
hash = hash % ht->array_size;
return hash;
}
static char PolicySignatureCompareFunc(
void *data1, uint16_t datalen1, void *data2, uint16_t datalen2)
{
const Signature *s1 = data1;
const Signature *s2 = data2;
if (s1 == NULL || s2 == NULL)
return 0;
return s1->flags == s2->flags && s1->alproto == s2->alproto &&
s1->app_progress_hook == s2->app_progress_hook;
}
static void PolicySignatureHashFree(void *data)
{
Signature *s = data;
SCFree(s->msg);
SCFree(s);
}
const char *ActionScopeToString(enum ActionScope s)
{
switch (s) {
case ACTION_SCOPE_PACKET:
return "packet";
case ACTION_SCOPE_FLOW:
return "flow";
case ACTION_SCOPE_HOOK:
return "hook";
case ACTION_SCOPE_TX:
return "tx";
case ACTION_SCOPE_AUTO:
return "auto";
}
DEBUG_VALIDATE_BUG_ON(1);
return "unknown";
}
void DetectFirewallPolicyToString(const struct DetectFirewallPolicy *p, char *out, size_t out_size)
{
const char *as = ActionScopeToString(p->action_scope);
DEBUG_VALIDATE_BUG_ON(as == NULL);
if (as == NULL)
return;
if (p->action & ACTION_REJECT_ANY) {
if (p->action & ACTION_REJECT_DST) {
snprintf(out, out_size, "rejectdst:%s", as);
} else if (p->action & ACTION_REJECT_BOTH) {
snprintf(out, out_size, "rejectboth:%s", as);
} else {
snprintf(out, out_size, "rejectsrc:%s", as);
}
} else if (p->action & ACTION_DROP) {
snprintf(out, out_size, "drop:%s", as);
} else if (p->action & ACTION_ACCEPT) {
snprintf(out, out_size, "accept:%s", as);
} else {
DEBUG_VALIDATE_BUG_ON(1);
}
if (p->action & ACTION_PASS) {
if (p->action_scope == ACTION_SCOPE_FLOW) {
strlcat(out, ",pass:flow", out_size);
} else {
DEBUG_VALIDATE_BUG_ON(1);
}
}
if (p->action & ACTION_ALERT) {
strlcat(out, ",alert", out_size);
}
}
static int AddAppPolicySignature(HashTable *ht, const int direction, const AppProto alproto,
const char *app_name, const uint8_t hook, const char *hookname,
struct DetectFirewallPolicy *pol)
{
Signature *s = SCCalloc(1, sizeof(*s)); // SigAlloc does way more than we need
if (s == NULL)
return -1;
char msg[256];
snprintf(msg, sizeof(msg), "SURICATA FW default app policy");
s->msg = SCStrdup(msg);
if (s->msg == NULL) {
SCFree(s);
return -1;
}
s->app_progress_hook = hook;
s->action = pol->action;
s->action_scope = pol->action_scope;
s->alproto = alproto;
s->flags = (direction == STREAM_TOSERVER) ? SIG_FLAG_TOSERVER : SIG_FLAG_TOCLIENT;
s->flags |= SIG_FLAG_FIREWALL;
s->type = SIG_TYPE_APP_TX;
s->detect_table = DETECT_TABLE_APP_FILTER;
s->id = 2201001;
s->rev = 1;
s->gid = 1;
s->prio = 3;
if (HashTableAdd(ht, s, 0) != 0) {
SCFree(s->msg);
SCFree(s);
return -1;
}
SCLogDebug("added to hash");
return 0;
}
static int DoParsePolicy(const char *policy_name, struct DetectFirewallPolicy *pol)
{
SCConfNode *policy_actions = SCConfGetNode(policy_name);
@ -3773,7 +3889,7 @@ static int DoParsePolicy(const char *policy_name, struct DetectFirewallPolicy *p
static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const char *hookname,
const uint8_t state, const uint8_t complete_state, const int direction,
struct DetectFirewallAppPolicy *app_fw_policies)
struct DetectFirewallPolicies *fw_policies, struct DetectFirewallAppPolicy *app_fw_policies)
{
char policy_name[256];
const char *in_name = hookname;
@ -3807,10 +3923,12 @@ static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const
FatalError("internal error: failed to assemble firewall policy config string");
}
struct DetectFirewallPolicy *pol;
if (direction == STREAM_TOSERVER)
r = DoParsePolicy(policy_name, &app_fw_policies[app_proto].ts[state]);
pol = &app_fw_policies[app_proto].ts[state];
else
r = DoParsePolicy(policy_name, &app_fw_policies[app_proto].tc[state]);
pol = &app_fw_policies[app_proto].tc[state];
r = DoParsePolicy(policy_name, pol);
if (r == 0 && in_name != NULL) {
if (state == 0) {
if (direction == STREAM_TOSERVER)
@ -3830,10 +3948,14 @@ static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const
FatalError("internal error: failed to assemble firewall policy config string");
}
if (direction == STREAM_TOSERVER)
return DoParsePolicy(policy_name, &app_fw_policies[app_proto].ts[state]);
else
return DoParsePolicy(policy_name, &app_fw_policies[app_proto].tc[state]);
r = DoParsePolicy(policy_name, pol);
}
/* for policies with an alert action, create a policy sig */
if (r == 1 && pol->action & ACTION_ALERT) {
SCLogDebug("adding policy signature");
return AddAppPolicySignature(fw_policies->policy_signatures, direction, app_proto, app_name,
state, hookname, pol);
}
return r;
}
@ -3848,6 +3970,10 @@ int DetectFirewallInitDefaultPolicies(DetectEngineCtx *de_ctx)
struct DetectFirewallAppPolicy *app_fw_policies = fw_policies->app;
if (app_fw_policies == NULL)
goto error;
fw_policies->policy_signatures = HashTableInit(
512, PolicySignatureHashFunc, PolicySignatureCompareFunc, PolicySignatureHashFree);
if (fw_policies->policy_signatures == NULL)
goto error;
fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER].action = ACTION_DROP;
fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER].action_scope = ACTION_SCOPE_PACKET;
@ -3924,7 +4050,7 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx)
const char *name =
AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOSERVER);
if (DoParseAppPolicy(prefix, a, name, state, complete_state_ts, STREAM_TOSERVER,
app_fw_policies) < 0)
fw_policies, app_fw_policies) < 0)
return -1;
}
@ -3934,7 +4060,7 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx)
const char *name =
AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOCLIENT);
if (DoParseAppPolicy(prefix, a, name, state, complete_state_tc, STREAM_TOCLIENT,
app_fw_policies) < 0)
fw_policies, app_fw_policies) < 0)
return -1;
}
}
@ -3942,6 +4068,22 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx)
return 0;
}
Signature *DetectFirewallGetPolicySignature(struct DetectFirewallPolicies *fw_policies,
const AppProto alproto, const int direction, const uint8_t hook)
{
if (fw_policies != NULL && fw_policies->policy_signatures != NULL) {
Signature lookup;
lookup.alproto = alproto;
lookup.flags = SIG_FLAG_FIREWALL |
(direction == STREAM_TOSERVER ? SIG_FLAG_TOSERVER : SIG_FLAG_TOCLIENT);
lookup.app_progress_hook = hook;
Signature *s = HashTableLookup(fw_policies->policy_signatures, &lookup, 0);
return s;
}
return NULL;
}
/*
* TESTS
*/

@ -24,6 +24,7 @@
#ifndef SURICATA_DETECT_PARSE_H
#define SURICATA_DETECT_PARSE_H
#include "action-globals.h"
#include "app-layer-protos.h"
#include "detect-engine-register.h"
// types from detect.h with only forward declarations for bindgen
@ -32,6 +33,7 @@ typedef struct Signature_ Signature;
typedef struct SigMatchCtx_ SigMatchCtx;
typedef struct SigMatch_ SigMatch;
typedef struct SigMatchData_ SigMatchData;
struct DetectFirewallPolicies;
/** Flags to indicate if the Signature parsing must be done
* switching the source and dest (for ip addresses and ports)
@ -114,7 +116,13 @@ int SC_Pcre2SubstringGet(pcre2_match_data *match_data, uint32_t number, PCRE2_UC
void DetectRegisterAppLayerHookLists(void);
void DetectListSupportedProtocols(void);
const char *ActionScopeToString(enum ActionScope s);
struct DetectFirewallPolicy;
void DetectFirewallPolicyToString(const struct DetectFirewallPolicy *p, char *out, size_t out_size);
int DetectFirewallInitDefaultPolicies(DetectEngineCtx *);
int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *);
Signature *DetectFirewallGetPolicySignature(struct DetectFirewallPolicies *fw_policies,
const AppProto alproto, const int direction, const uint8_t hook);
#endif /* SURICATA_DETECT_PARSE_H */

@ -36,6 +36,7 @@
#include "app-layer-frames.h"
#include "detect.h"
#include "detect-parse.h"
#include "detect-dsize.h"
#include "detect-engine.h"
#include "detect-engine-build.h"
@ -1640,6 +1641,20 @@ struct DetectFirewallAppTxState {
bool last_fw_rule; /**< processing the last fw rule, so we need to eval all hooks after it. */
};
static inline void DetectRunAppendDefaultAppPolicyAlert(DetectEngineThreadCtx *det_ctx, Packet *p,
const bool apply_to_packet, const int direction, const uint64_t tx_id,
const AppProto alproto, const uint8_t hook)
{
if (EngineModeIsFirewall()) {
Signature *s = DetectFirewallGetPolicySignature(
det_ctx->de_ctx->fw_policies, alproto, direction, hook);
BUG_ON(s == NULL);
uint8_t alert_flags = apply_to_packet ? PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET : 0;
alert_flags |= PACKET_ALERT_FLAG_TX;
AlertQueueAppend(det_ctx, s, p, tx_id, alert_flags);
}
}
/** \internal
* \brief apply default policy
* \param p packet to apply policy to
@ -1650,8 +1665,8 @@ struct DetectFirewallAppTxState {
* to look up configurable default policies later
*/
static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy(
const struct DetectFirewallAppPolicy *policies, Packet *p, const AppProto alproto,
const uint8_t direction, const uint8_t progress)
DetectEngineThreadCtx *det_ctx, const struct DetectFirewallAppPolicy *policies, Packet *p,
const AppProto alproto, const uint8_t direction, const uint8_t progress)
{
const struct DetectFirewallPolicy *policy;
if (direction & STREAM_TOSERVER) {
@ -1708,30 +1723,56 @@ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies(
direction & STREAM_TOSERVER ? "toserver" : "toclient", hook);
const struct DetectFirewallPolicy *policy = DetectFirewallApplyDefaultAppPolicy(
det_ctx->de_ctx->fw_policies->app, p, alproto, direction, hook);
SCLogDebug("fw: hook:%u policy:%02x", hook, policy->action);
det_ctx, det_ctx->de_ctx->fw_policies->app, p, alproto, direction, hook);
SCLogDebug("fw: hook:%u policy:%02x apply_to_packet:%s", hook, policy->action,
BOOL2STR(apply_to_packet));
actions |= policy->action;
if (policy->action & ACTION_DROP) {
SCLogDebug("fw: action %02x", policy->action);
return DETECT_TX_FW_FC_BREAK;
} else if (policy->action == ACTION_ACCEPT) {
SCLogDebug("fw: accept hook %u", hook);
} else if (policy->action & ACTION_ACCEPT) {
SCLogDebug("fw: accept hook %u action %02x", hook, policy->action);
/* accepting flow, so skip rest of the fw rules */
if (policy->action_scope == ACTION_SCOPE_FLOW) {
if (policy->action & ACTION_ALERT) {
DetectRunAppendDefaultAppPolicyAlert(
det_ctx, p, is_last, direction, tx->tx_id, alproto, hook);
} else {
DetectRunAppendDefaultAccept(det_ctx, p);
}
return DETECT_TX_FW_FC_SKIP;
} else if (policy->action_scope == ACTION_SCOPE_TX) {
tx->tx_data_ptr->flags |= APP_LAYER_TX_ACCEPT;
SCLogDebug("ACTION_SCOPE_TX, setting APP_LAYER_TX_ACCEPT");
if (is_last) {
if (policy->action & ACTION_ALERT) {
DetectRunAppendDefaultAppPolicyAlert(
det_ctx, p, is_last, direction, tx->tx_id, alproto, hook);
} else if (is_last) {
DetectRunAppendDefaultAccept(det_ctx, p);
SCLogDebug("DetectRunAppendDefaultAccept for last tx");
}
return DETECT_TX_FW_FC_SKIP;
} else if (policy->action_scope == ACTION_SCOPE_HOOK) {
if (policy->action & ACTION_ALERT) {
DetectRunAppendDefaultAppPolicyAlert(
det_ctx, p, is_last, direction, tx->tx_id, alproto, hook);
} else if (is_last) {
DetectRunAppendDefaultAccept(det_ctx, p);
SCLogDebug("DetectRunAppendDefaultAccept for last tx");
}
}
} else {
if (policy->action & ACTION_ALERT) {
DetectRunAppendDefaultAppPolicyAlert(
det_ctx, p, is_last, direction, tx->tx_id, alproto, hook);
}
}
}
if ((is_last && (actions & (ACTION_ACCEPT | ACTION_DROP)) == ACTION_ACCEPT)) {
DetectRunAppendDefaultAccept(det_ctx, p);
}
@ -2384,9 +2425,9 @@ static void DetectRunTx(ThreadVars *tv,
s->app_progress_hook);
/* if this rule was the last for our progress state, and it didn't match,
* we have to invoke the default policy. We only check the current rule hook. */
const struct DetectFirewallPolicy *policy =
DetectFirewallApplyDefaultAppPolicy(det_ctx->de_ctx->fw_policies->app, p,
s->alproto, flow_flags, s->app_progress_hook);
const struct DetectFirewallPolicy *policy = DetectFirewallApplyDefaultAppPolicy(
det_ctx, det_ctx->de_ctx->fw_policies->app, p, s->alproto, flow_flags,
s->app_progress_hook);
SCLogDebug("fw_last_for_progress policy %02x", policy->action);
if (policy->action & ACTION_DROP) {
fw_state.fw_skip_app_filter = true;

@ -935,6 +935,9 @@ struct DetectFirewallPolicies {
/** policy for packet_filter, pre_flow, pre_stream hooks */
struct DetectFirewallPolicy pkt[DETECT_FIREWALL_POLICY_SIZE];
/* hash table with a Signature object per default policy that has `alert` enabled. */
HashTable *policy_signatures;
/** app layer policies, one per alproto */
struct DetectFirewallAppPolicy app[];
};

Loading…
Cancel
Save