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.
This commit is contained in:
@@ -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)
|
||||
{
|
||||
this->last_cookie = now;
|
||||
return FALSE;
|
||||
}
|
||||
if (src)
|
||||
{
|
||||
/* 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);
|
||||
|
||||
Reference in New Issue
Block a user