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);