From 61cdd9be6bb8e2b7b5e62a81c3d56c9080d63e9a Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 7 Nov 2013 22:55:15 +0100 Subject: [PATCH] dns: detect case of request flooding 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 | 2 ++ src/app-layer-dns-common.c | 33 +++++++++++++++++++++++++++-- src/app-layer-dns-common.h | 8 +++++++ src/app-layer-dns-tcp.c | 16 ++++++-------- src/app-layer-dns-udp.c | 43 ++++++++++++++++++++++++++------------ src/util-error.h | 1 + 6 files changed, 78 insertions(+), 25 deletions(-) diff --git a/rules/dns-events.rules b/rules/dns-events.rules index 030628d56b..a14a9030c3 100644 --- a/rules/dns-events.rules +++ b/rules/dns-events.rules @@ -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;) diff --git a/src/app-layer-dns-common.c b/src/app-layer-dns-common.c index 3cf0317cae..b56593b54c 100644 --- a/src/app-layer-dns-common.c +++ b/src/app-layer-dns-common.c @@ -28,12 +28,26 @@ #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 diff --git a/src/app-layer-dns-common.h b/src/app-layer-dns-common.h index acbc2ec010..e04986f347 100644 --- a/src/app-layer-dns-common.h +++ b/src/app-layer-dns-common.h @@ -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); diff --git a/src/app-layer-dns-tcp.c b/src/app-layer-dns-tcp.c index 522dbb5b0f..6fbb4fc8cc 100644 --- a/src/app-layer-dns-tcp.c +++ b/src/app-layer-dns-tcp.c @@ -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: diff --git a/src/app-layer-dns-udp.c b/src/app-layer-dns-udp.c index d68791e386..4acd168f98 100644 --- a/src/app-layer-dns-udp.c +++ b/src/app-layer-dns-udp.c @@ -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); diff --git a/src/util-error.h b/src/util-error.h index d9d58c0816..ea76fcbc42 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -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);