]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
dns: detect case of request flooding
authorVictor Julien <victor@inliniac.net>
Thu, 7 Nov 2013 21:55:15 +0000 (22:55 +0100)
committerVictor Julien <victor@inliniac.net>
Fri, 15 Nov 2013 14:41:24 +0000 (15:41 +0100)
In the case where DNS requests are sent over the same flow w/o a
reply being received, we now set an event in the flow and refuse
to add more transactions to the state. This protects the DNS
handling from getting overloaded slowing down everything.

A new option to configure this behaviour was added:

app-layer:
  protocols:
    dnsudp:
       enabled: yes
       detection-ports:
         udp:
           toserver: 53
       request-flood: 750

The request-flood parameter can be 0 (disabling this feature) or a
positive integer. It defaults to 500.

This means that if 500 unreplied requests are seen in a row an event
is set. Rule 2240007 was added to dns-events.rules to match on this.

rules/dns-events.rules
src/app-layer-dns-common.c
src/app-layer-dns-common.h
src/app-layer-dns-tcp.c
src/app-layer-dns-udp.c
src/util-error.h

index 030628d56b1df97cc077aafd3f5a75bea3954d0e..a14a9030c3a357d30b407081da9d9bc4d4ac688b 100644 (file)
@@ -9,3 +9,5 @@ alert dns any any -> any any (msg:"SURICATA DNS Not a request"; flow:to_server;
 alert dns any any -> any any (msg:"SURICATA DNS Not a response"; flow:to_client; app-layer-event:dns.not_a_response; sid:2240005; rev:1;)
 # Z flag (reserved) not 0
 alert dns any any -> any any (msg:"SURICATA DNS Z flag set"; app-layer-event:dns.z_flag_set; sid:2240006; rev:1;)
+# Request Flood Detected
+alert dns any any -> any any (msg:"SURICATA DNS request flood detected"; flow:to_server; app-layer-event:dns.flooded; sid:2240007; rev:1;)
index 3cf0317cae7812073b8186c64c0f2afba54c431a..b56593b54cd2de67275374cfe3f6e39d4301a1b3 100644 (file)
 #include "util-print.h"
 #endif
 
+typedef struct DNSConfig_ {
+    uint32_t request_flood;
+} DNSConfig;
+static DNSConfig dns_config;
+
+void DNSConfigInit(void) {
+    memset(&dns_config, 0x00, sizeof(dns_config));
+}
+
+void DNSConfigSetRequestFlood(uint32_t value) {
+    dns_config.request_flood = value;
+}
+
 SCEnumCharMap dns_decoder_event_table[ ] = {
     { "UNSOLLICITED_RESPONSE",      DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE, },
     { "MALFORMED_DATA",             DNS_DECODER_EVENT_MALFORMED_DATA, },
     { "NOT_A_REQUEST",              DNS_DECODER_EVENT_NOT_A_REQUEST, },
     { "NOT_A_RESPONSE",             DNS_DECODER_EVENT_NOT_A_RESPONSE, },
     { "Z_FLAG_SET",                 DNS_DECODER_EVENT_Z_FLAG_SET, },
+    { "FLOODED",                    DNS_DECODER_EVENT_FLOODED, },
 
     { NULL,                         -1 },
 };
@@ -312,8 +326,21 @@ bad_data:
 void DNSStoreQueryInState(DNSState *dns_state, const uint8_t *fqdn, const uint16_t fqdn_len,
         const uint16_t type, const uint16_t class, const uint16_t tx_id)
 {
-    if (dns_state->curr != NULL && dns_state->curr->replied == 0)
+    /* flood protection */
+    if (dns_state->givenup)
+        return;
+
+    if (dns_state->curr != NULL && dns_state->curr->replied == 0) {
         dns_state->curr->reply_lost = 1;
+        dns_state->unreplied_cnt++;
+
+        /* check flood limit */
+        if (dns_config.request_flood != 0 &&
+            dns_state->unreplied_cnt > dns_config.request_flood) {
+            DNSSetEvent(dns_state, DNS_DECODER_EVENT_FLOODED);
+            dns_state->givenup = 1;
+        }
+    }
 
     DNSTransaction *tx = DNSTransactionFindByTxId(dns_state, tx_id);
     if (tx == NULL) {
@@ -353,7 +380,6 @@ void DNSStoreAnswerInState(DNSState *dns_state, const int rtype, const uint8_t *
         TAILQ_INSERT_TAIL(&dns_state->tx_list, tx, next);
         dns_state->curr = tx;
         tx->tx_num = dns_state->transaction_max;
-
     }
 
     DNSAnswerEntry *q = SCMalloc(sizeof(DNSAnswerEntry) + fqdn_len + data_len);
@@ -381,6 +407,9 @@ void DNSStoreAnswerInState(DNSState *dns_state, const int rtype, const uint8_t *
 
     /* mark tx is as replied so we can log it */
     tx->replied = 1;
+
+    /* reset unreplied counter */
+    dns_state->unreplied_cnt = 0;
 }
 
 /** \internal
index acbc2ec010c5af90f039c634200f5ce02f3776f0..e04986f34764e912fb9c8a7d41b22add152cfa7c 100644 (file)
@@ -56,6 +56,7 @@ enum {
     DNS_DECODER_EVENT_NOT_A_REQUEST,
     DNS_DECODER_EVENT_NOT_A_RESPONSE,
     DNS_DECODER_EVENT_Z_FLAG_SET,
+    DNS_DECODER_EVENT_FLOODED,
 };
 
 /** \brief DNS packet header */
@@ -143,7 +144,9 @@ typedef struct DNSState_ {
     TAILQ_HEAD(, DNSTransaction_) tx_list;  /**< transaction list */
     DNSTransaction *curr;                   /**< ptr to current tx */
     uint64_t transaction_max;
+    uint32_t unreplied_cnt;                 /**< number of unreplied requests in a row */
     uint16_t events;
+    uint16_t givenup;
 
     /* used by TCP only */
     uint16_t offset;
@@ -151,6 +154,11 @@ typedef struct DNSState_ {
     uint8_t *buffer;
 } DNSState;
 
+#define DNS_CONFIG_DEFAULT_REQUEST_FLOOD 500
+
+void DNSConfigInit(void);
+void DNSConfigSetRequestFlood(uint32_t value);
+
 void RegisterDNSParsers(void);
 void DNSParserTests(void);
 void DNSParserRegisterTests(void);
index 522dbb5b0f8774ff048b61baaf6ed7414035298a..6fbb4fc8cc80d097fbbb16d7abeb1b614f289615 100644 (file)
@@ -359,11 +359,12 @@ static int DNSReponseParseData(Flow *f, DNSState *dns_state, const uint8_t *inpu
 
     DNSTransaction *tx = NULL;
     int found = 0;
-    TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
-        if (tx->tx_id == ntohs(dns_header->tx_id)) {
-            found = 1;
-            break;
-        }
+    if ((tx = DNSTransactionFindByTxId(dns_state, ntohs(dns_header->tx_id))) != NULL)
+        found = 1;
+
+    if (!found) {
+        SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
+        DNSSetEvent(dns_state, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
     }
 
     uint16_t q;
@@ -439,11 +440,6 @@ static int DNSReponseParseData(Flow *f, DNSState *dns_state, const uint8_t *inpu
         }
     }
 
-    if (!found) {
-        SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
-        DNSSetEvent(dns_state, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
-    }
-
        SCReturnInt(1);
 bad_data:
 insufficient_data:
index d68791e386f8e6357e02cce371d7e74d5b4f6330..4acd168f981e90f529aaf1f89b802118e283f2c7 100644 (file)
@@ -23,6 +23,9 @@
 #include "suricata-common.h"
 #include "suricata.h"
 
+#include "conf.h"
+#include "util-misc.h"
+
 #include "debug.h"
 #include "decode.h"
 
@@ -179,12 +182,14 @@ static int DNSUDPResponseParse(Flow *f, void *dstate,
 
     DNSTransaction *tx = NULL;
     int found = 0;
-    TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
-        if (tx->tx_id == ntohs(dns_header->tx_id)) {
-            found = 1;
-            break;
-        }
+    if ((tx = DNSTransactionFindByTxId(dns_state, ntohs(dns_header->tx_id))) != NULL)
+        found = 1;
+
+    if (!found) {
+        SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
+        DNSSetEvent(dns_state, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
     }
+
     if (DNSValidateResponseHeader(dns_state, dns_header) < 0)
         goto bad_data;
 
@@ -269,17 +274,12 @@ static int DNSUDPResponseParse(Flow *f, void *dstate,
     if (ntohs(dns_header->flags) & 0x0003) {
         SCLogDebug("no such name");
 
-        if (dns_state->curr != NULL) {
-            dns_state->curr->no_such_name = 1;
+        if (tx != NULL) {
+            tx->no_such_name = 1;
         }
     }
 
-    if (!found) {
-        SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
-        DNSSetEvent(dns_state, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
-    }
-
-       SCReturnInt(1);
+    SCReturnInt(1);
 
 bad_data:
 insufficient_data:
@@ -300,6 +300,21 @@ static uint16_t DNSUdpProbingParser(uint8_t *input, uint32_t ilen, uint32_t *off
     return ALPROTO_DNS_UDP;
 }
 
+static void DNSUDPConfigure(void) {
+    uint32_t request_flood = DNS_CONFIG_DEFAULT_REQUEST_FLOOD;
+
+    ConfNode *p = ConfGetNode("app-layer.protocols.dnsudp.request-flood");
+    if (p != NULL) {
+        uint32_t value;
+        if (ParseSizeStringU32(p->val, &value) < 0) {
+            SCLogError(SC_ERR_DNS_CONFIG, "invalid value for request-flood %s", p->val);
+        } else {
+            request_flood = value;
+        }
+    }
+    SCLogInfo("DNS request flood protection level: %u", request_flood);
+    DNSConfigSetRequestFlood(request_flood);
+}
 
 void RegisterDNSUDPParsers(void) {
     char *proto_name = "dnsudp";
@@ -349,6 +364,8 @@ void RegisterDNSUDPParsers(void) {
                                                            DNSGetAlstateProgressCompletionStatus);
 
         DNSAppLayerRegisterGetEventInfo(ALPROTO_DNS_UDP);
+
+        DNSUDPConfigure();
     } else {
         SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
                   "still on.", proto_name);
index d9d58c0816bce5c769dee82e0bcafa4e65a112bf..ea76fcbc42c8841104abacaa66a5cb4b327212b8 100644 (file)
@@ -268,6 +268,7 @@ typedef enum {
     SC_WARN_XFF_INVALID_MODE,
     SC_WARN_XFF_INVALID_HEADER,
     SC_ERR_THRESHOLD_SETUP,
+    SC_ERR_DNS_CONFIG,
 } SCError;
 
 const char *SCErrorToString(SCError);