From: Tobias Brunner Date: Fri, 18 Mar 2022 14:13:06 +0000 (+0100) Subject: receiver: Add per-IP cookie threshold X-Git-Tag: 5.9.6rc1~6 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d8104b7c69a1c0e47762ee795150aa41c5e4b07c;p=thirdparty%2Fstrongswan.git receiver: Add per-IP cookie threshold Because the global cookie threshold is higher than the per-IP block threshold, it was previously possible for an attacker to block a legitimate user by sending spoofed IKE_SA_INIT packets from that user's IP. The timespan for requiring cookies is now also not extended anymore with every IKE_SA_INIT received during the calm down period. Because this allowed an attacker, after initially triggering the global cookie threshold, to force cookies for all clients by sending just a single spoofed IKE_SA_INIT every 10 seconds. We keep track of reaching the per-IP threshold in segments of the hashed IP addresses, so only a (random, due to chunk_hash()'s random key) subset of clients will receive cookies, if single IPs are targeted. The default global threshold is increased a bit. --- diff --git a/conf/options/charon.opt b/conf/options/charon.opt index df32330469..8126f990f0 100644 --- a/conf/options/charon.opt +++ b/conf/options/charon.opt @@ -69,9 +69,13 @@ charon.cisco_unity = no charon.close_ike_on_child_failure = no Close the IKE_SA if setup of the CHILD_SA along with IKE_AUTH failed. -charon.cookie_threshold = 10 +charon.cookie_threshold = 30 Number of half-open IKE_SAs that activate the cookie mechanism. +charon.cookie_threshold_ip = 3 + Number of half-open IKE_SAs for a single peer IP that activate the cookie + mechanism. + charon.crypto_test.bench = no Benchmark crypto algorithms and order them by efficiency. diff --git a/src/libcharon/network/receiver.c b/src/libcharon/network/receiver.c index 8ab413e64a..9a786bc34e 100644 --- a/src/libcharon/network/receiver.c +++ b/src/libcharon/network/receiver.c @@ -33,11 +33,18 @@ #define COOKIE_LIFETIME 10 /** time we wait before disabling cookies */ #define COOKIE_CALMDOWN_DELAY 10 +/** number of per-IP timestamps we keep track of (must be a power of 2) */ +#define COOKIE_CALMDOWN_BUCKETS 32 +/** mask applied to IP address hashes to determine the timestamp index */ +#define COOKIE_CALMDOWN_MASK (COOKIE_CALMDOWN_BUCKETS-1) /** time in seconds we use a secret at most (since we keep two secrets, must * be at least COOKIE_LIFETIME to process all outstanding valid cookies) */ #define COOKIE_SECRET_SWITCH 120 /** default value for private_receiver_t.cookie_threshold */ -#define COOKIE_THRESHOLD_DEFAULT 10 +#define COOKIE_THRESHOLD_DEFAULT 30 +/** default value for private_receiver_t.cookie_threshold_ip (must be lower + * than BLOCK_THRESHOLD_DEFAULT) */ +#define COOKIE_THRESHOLD_IP_DEFAULT 3 /** default value for private_receiver_t.block_threshold */ #define BLOCK_THRESHOLD_DEFAULT 5 /** length of the secret to use for cookie calculation */ @@ -102,17 +109,23 @@ struct private_receiver_t { /** * require cookies after this many half open IKE_SAs */ - uint32_t cookie_threshold; + u_int cookie_threshold; /** - * timestamp of last cookie requested + * require cookies for a specific IP after this many half open IKE_SAs */ - time_t last_cookie; + u_int cookie_threshold_ip; + + /** + * global (0) and per-IP hash (1..n) timestamps of when the threshold was + * last reached + */ + time_t last_threshold[COOKIE_CALMDOWN_BUCKETS+1]; /** * how many half open IKE_SAs per peer before blocking */ - uint32_t block_threshold; + u_int block_threshold; /** * Drop IKE_SA_INIT requests if processor job load exceeds this limit @@ -283,30 +296,67 @@ static bool check_cookie(private_receiver_t *this, message_t *message) } /** - * Check if we currently require cookies + * Struct to keep track of half-open SA counts + */ +typedef struct { + u_int count; + bool determined; +} half_open_count_t; + +/** + * Get the number of half-open SAs, cached or from the manager, either global + * or for a single IP. + */ +static u_int get_half_open_count(half_open_count_t *this, host_t *src) +{ + if (!this->determined) + { + this->determined = TRUE; + this->count = charon->ike_sa_manager->get_half_open_count( + charon->ike_sa_manager, src, TRUE); + } + return this->count; +} + +/** + * Check if we currently require cookies either globally or for the given IP */ static bool cookie_required(private_receiver_t *this, - u_int half_open, uint32_t now) + half_open_count_t *half_open, host_t *src, + uint32_t now) { - if (this->cookie_threshold && half_open >= this->cookie_threshold) + u_int threshold = src ? this->cookie_threshold_ip : this->cookie_threshold; + u_int idx = 0; + + if (!threshold) + { + return FALSE; + } + if (src) { - this->last_cookie = now; + /* keep track of IPs in segments so not all are affected if a single + * IP is targeted */ + idx = 1 + (chunk_hash(src->get_address(src)) & COOKIE_CALMDOWN_MASK); + } + if (get_half_open_count(half_open, src) >= threshold) + { + this->last_threshold[idx] = now; return TRUE; } - if (this->last_cookie && now < this->last_cookie + COOKIE_CALMDOWN_DELAY) + if (this->last_threshold[idx] && + now < this->last_threshold[idx] + COOKIE_CALMDOWN_DELAY) { - /* We don't disable cookies unless we haven't seen IKE_SA_INITs - * for COOKIE_CALMDOWN_DELAY seconds. This avoids jittering between + /* We don't disable cookies unless the threshold was not reached for + * COOKIE_CALMDOWN_DELAY seconds. This avoids jittering between * cookie on / cookie off states, which is problematic. Consider the - * following: A legitimate initiator sends a IKE_SA_INIT while we + * following: A legitimate initiator sends an IKE_SA_INIT while we * are under a DoS attack. If we toggle our cookie behavior, * multiple retransmits of this IKE_SA_INIT might get answered with * and without cookies. The initiator goes on and retries with * a cookie, but it can't know if the completing IKE_SA_INIT response * is to its IKE_SA_INIT request with or without cookies. This is - * problematic, as the cookie is part of AUTH payload data. + * problematic, as the cookie is part of the AUTH payload data. */ - this->last_cookie = now; return TRUE; } return FALSE; @@ -317,22 +367,23 @@ static bool cookie_required(private_receiver_t *this, */ static bool drop_ike_sa_init(private_receiver_t *this, message_t *message) { - u_int half_open; + half_open_count_t half_open = {}, half_open_ip = {}; + host_t *src; uint32_t now; + src = message->get_source(message); now = time_monotonic(NULL); - half_open = charon->ike_sa_manager->get_half_open_count( - charon->ike_sa_manager, NULL, TRUE); /* check for cookies in IKEv2 */ if (message->get_major_version(message) == IKEV2_MAJOR_VERSION && - cookie_required(this, half_open, now) && !check_cookie(this, message)) + (cookie_required(this, &half_open, NULL, now) || + cookie_required(this, &half_open_ip, src, now)) && + !check_cookie(this, message)) { chunk_t cookie; DBG2(DBG_NET, "received packet: from %#H to %#H (%zu bytes)", - message->get_source(message), - message->get_destination(message), + src, message->get_destination(message), message->get_packet_data(message).len); if (!this->secret_first_use) @@ -362,8 +413,7 @@ static bool drop_ike_sa_init(private_receiver_t *this, message_t *message) { return TRUE; } - DBG2(DBG_NET, "sending COOKIE notify to %H", - message->get_source(message)); + DBG2(DBG_NET, "sending COOKIE notify to %H", src); send_notify(message, IKEV2_MAJOR_VERSION, IKE_SA_INIT, COOKIE, cookie); chunk_free(&cookie); return TRUE; @@ -371,21 +421,20 @@ static bool drop_ike_sa_init(private_receiver_t *this, message_t *message) /* check if peer has too many IKE_SAs half open */ if (this->block_threshold && - charon->ike_sa_manager->get_half_open_count(charon->ike_sa_manager, - message->get_source(message), TRUE) >= this->block_threshold) + get_half_open_count(&half_open_ip, src) >= this->block_threshold) + { - DBG1(DBG_NET, "ignoring IKE_SA setup from %H, " - "peer too aggressive", message->get_source(message)); + DBG1(DBG_NET, "ignoring IKE_SA setup from %H, per-IP half-open IKE_SA " + "limit of %d reached", src, this->block_threshold); return TRUE; } /* check if global half open IKE_SA limit reached */ if (this->init_limit_half_open && - half_open >= this->init_limit_half_open) + get_half_open_count(&half_open, NULL) >= this->init_limit_half_open) { - DBG1(DBG_NET, "ignoring IKE_SA setup from %H, half open IKE_SA " - "count of %d exceeds limit of %d", message->get_source(message), - half_open, this->init_limit_half_open); + DBG1(DBG_NET, "ignoring IKE_SA setup from %H, half-open IKE_SA " + "limit of %d reached", src, this->init_limit_half_open); return TRUE; } @@ -401,8 +450,7 @@ static bool drop_ike_sa_init(private_receiver_t *this, message_t *message) if (jobs > this->init_limit_job_load) { DBG1(DBG_NET, "ignoring IKE_SA setup from %H, job load of %d " - "exceeds limit of %d", message->get_source(message), - jobs, this->init_limit_job_load); + "exceeds limit of %d", src, jobs, this->init_limit_job_load); return TRUE; } } @@ -487,8 +535,7 @@ static job_requeue_t receive_packets(private_receiver_t *this) message = message_create_from_packet(packet); if (message->parse_header(message) != SUCCESS) { - DBG1(DBG_NET, "received invalid IKE header from %H - ignored", - packet->get_source(packet)); + DBG1(DBG_NET, "received invalid IKE header from %H - ignored", src); charon->bus->alert(charon->bus, ALERT_PARSE_ERROR_HEADER, message); message->destroy(message); return JOB_REQUEUE_DIRECT; @@ -535,7 +582,7 @@ static job_requeue_t receive_packets(private_receiver_t *this) { DBG1(DBG_NET, "received unsupported IKE version %d.%d from %H, sending " "INVALID_MAJOR_VERSION", message->get_major_version(message), - message->get_minor_version(message), packet->get_source(packet)); + message->get_minor_version(message), src); message->destroy(message); return JOB_REQUEUE_DIRECT; } @@ -637,8 +684,18 @@ receiver_t *receiver_create() { this->cookie_threshold = lib->settings->get_int(lib->settings, "%s.cookie_threshold", COOKIE_THRESHOLD_DEFAULT, lib->ns); + this->cookie_threshold_ip = lib->settings->get_int(lib->settings, + "%s.cookie_threshold_ip", COOKIE_THRESHOLD_IP_DEFAULT, lib->ns); this->block_threshold = lib->settings->get_int(lib->settings, "%s.block_threshold", BLOCK_THRESHOLD_DEFAULT, lib->ns); + + if (this->cookie_threshold_ip >= this->block_threshold) + { + this->block_threshold = this->cookie_threshold_ip + 1; + DBG1(DBG_NET, "increasing block threshold to %u due to per-IP " + "cookie threshold of %u", this->block_threshold, + this->cookie_threshold_ip); + } } this->init_limit_job_load = lib->settings->get_int(lib->settings, "%s.init_limit_job_load", 0, lib->ns);