From patchwork Fri Dec 7 03:20:03 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 633 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id 8IY2B+mBClwUPwAAIUCqbw for ; Fri, 07 Dec 2018 09:21:29 -0500 Received: from proxy12.mail.iad3b.rsapps.net ([172.31.255.6]) by director7.mail.ord1d.rsapps.net with LMTP id YI8GBemBClyOWQAAovjBpQ ; Fri, 07 Dec 2018 09:21:29 -0500 Received: from smtp12.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy12.mail.iad3b.rsapps.net with LMTP id aLZAAOmBClwQaQAAEsW3lA ; Fri, 07 Dec 2018 09:21:29 -0500 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp12.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=greenie.muc.de X-Suspicious-Flag: YES X-Classification-ID: 622afcd4-fa2b-11e8-bbd8-525400ae1f9d-1-1 Received: from [216.105.38.7] ([216.105.38.7:62895] helo=lists.sourceforge.net) by smtp12.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id C2/A1-11923-7E18A0C5; Fri, 07 Dec 2018 09:21:28 -0500 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1gVGzL-0006uE-6e; Fri, 07 Dec 2018 14:20:19 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1gVGzI-0006u5-SX for openvpn-devel@lists.sourceforge.net; Fri, 07 Dec 2018 14:20:16 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=ewqOrrwdzR36/06ANP5P8yJoEy2U205YSIranNJ6v2k=; b=KLXLzTP3f0dJytfTU+uagmmHNS ujUVw+AMpifEcFQsl4oZldFXM0BZXtZwiElW/Vh06S85sAYipC4l7Bt8Yf9WNmxo4xhERQjHehamC YDckGLuJpxemMZwE8HPeqfpkQeN0H6AJHmnAY8ld6ZxLZT5GyF+UkSz6WJ7R73q+PV/w=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=ewqOrrwdzR36/06ANP5P8yJoEy2U205YSIranNJ6v2k=; b=ZPlYtepcmu1219zgMg5ZrXzsHF WQlm+iXkTmVVZUjJ1bT645bs4FWg5gW+AYG/fiZsYmEzBEbD/+4xHnyQyvl472L3+vquRVIUXFJ7f FYZ3ndJTy2qManbwSuCaMZdyrWMHbneMQWkjsRuiLwbz1TNW/pRJCr4EYhaEM+mwuJNM=; Received: from mariotte.space.net ([193.149.36.253] helo=mariotte2.space.net) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gVGzG-00988I-3u for openvpn-devel@lists.sourceforge.net; Fri, 07 Dec 2018 14:20:16 +0000 Received: from mariotte2.space.net (localhost [127.0.0.1]) by mariotte2.space.net (8.15.2/8.15.2) with ESMTPS id wB7EK3M6073217 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO) for ; Fri, 7 Dec 2018 15:20:03 +0100 (CET) (envelope-from gert@mariotte2.space.net) Received: (from gert@localhost) by mariotte2.space.net (8.15.2/8.15.2/Submit) id wB7EK3VL073216 for openvpn-devel@lists.sourceforge.net; Fri, 7 Dec 2018 15:20:03 +0100 (CET) (envelope-from gert) From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Fri, 7 Dec 2018 15:20:03 +0100 Message-Id: <20181207142003.73175-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181207123303.70827-1-gert@greenie.muc.de> References: <20181207123303.70827-1-gert@greenie.muc.de> MIME-Version: 1.0 X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.1 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1gVGzG-00988I-3u Subject: [Openvpn-devel] [PATCH v2] Stop state-exhaustion attacks from a single source address. X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox If an attacker sends lots of RESET packets from different source ports (but the same source address), every packet will create a new multi_instance, leading to resource waste on the server, and to lots of reply packets while OpenVPN tries to establish a TLS handshake. This can be rate-limited with "connect-freq", but if this is set too tightly, an attacker can drown out legitimate users of the same OpenVPN server. So, when deciding whether or not to create a new instance, iterate over all existing instances, counting all from the same source IP (ignoring source port info) that are "in TLS negotiation" state - if more than instances are already active, refuse new instance. The cutoff parameter can be configured by a newly introduced 3rd argument to "connect-freq". So something like this might be reasonable for a medium-sized server: connect-freq 20 20 3 ("permit 20 new connections per 20 seconds, but only 3 from the same source IP address") Drawback: if many users are sitting behind a shared NAT ip address and they all reconnect at the same time, session setup will take longer for some of the users while the server is still handshaking with others. v1: this is really a "request for discussion" and needs removal of lots of debugging printout. Also, it needs to be configurable. v2: make it configurable Signed-off-by: Gert Doering --- src/openvpn/mudp.c | 108 +++++++++++++++++++++++++++++++++++++++++- src/openvpn/options.c | 6 ++- src/openvpn/options.h | 1 + 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c index a604d217..ff5eb688 100644 --- a/src/openvpn/mudp.c +++ b/src/openvpn/mudp.c @@ -41,6 +41,111 @@ #include #endif +/* compare two mroute_addr, ignoring the port info + */ +static inline bool +mroute_addr_equal_no_port(const struct mroute_addr *a1, + const struct mroute_addr *a2) +{ + if (a1->type != a2->type) + { + return false; + } + if (a1->netbits != a2->netbits) + { + return false; + } + if (a1->len != a2->len) + { + return false; + } + int len = a1->len; + if ( len == 6 || len == 18 ) /* AF_INET or AF_INET6 + Port */ + { + len -= 2; + } + return memcmp(a1->raw_addr, a2->raw_addr, len) == 0; +} + +/* + * allow no more than instances for the same source IP + * in "key state not active yet" state - this is almost always a + * state exhaustion and/or reflection attack + * + * real use cases might hit "multiple instances for the same source IP" + * if there are multiple users behind the same NAT gateway - but those + * will not be in "ks->state < S_START" for more than a few seconds + * (so we might see a few false positives if many users behind the same + * NAT gateway restart their VPNs at the same time, but TLS handshake + * will succeed for one after another, eventually for all) + */ + + +bool +dangling_instances_per_source_allowed(struct multi_context *m, + struct mroute_addr *new_source) +{ + /* nothing configured -> default = allow all */ + if ( m->top.options.cf_max_dangling <= 0 ) + { + return true; + } + + struct gc_arena gc = gc_new(); + msg(M_INFO, "MULTI dipsa: new_source=%s (len=%d)", + mroute_addr_print(new_source, &gc), new_source->len ); + + struct hash_iterator hi; + struct hash_element *he; + int count = 0; + + hash_iterator_init(m->iter, &hi); + while ((he = hash_iterator_next(&hi))) + { + struct multi_instance *mi = (struct multi_instance *) he->value; + + msg(M_INFO, "MULTI dipsa: instance: halt=%d real=%s", + mi->halt, mroute_addr_print(&mi->real, &gc)); + if (!mi->halt && mroute_addr_equal_no_port(new_source, &mi->real)) + { + msg(M_INFO, "MULTI dipsa: ip match found!"); + + struct tls_multi * tls_multi = mi->context.c2.tls_multi; + + for (int i=0; isession[i].key[KS_PRIMARY].state ); + } + + /* session not fully established yet? + * (TM_ACTIVE seems to be the one that goes from "2" to "6" + * after successful handshake, while TM_UNTRUST sticks to "1") + */ + struct tls_session *session = &tls_multi->session[TM_ACTIVE]; + struct key_state *ks = &session->key[KS_PRIMARY]; + if ( ks->state < S_START ) + { + msg(M_INFO, "MULTI dipsa: dangling session (ks->state=%d)", + ks->state ); + count++; + } + } + } + hash_iterator_free(&hi); + + gc_free(&gc); + + msg( M_INFO, "MULTI dipsa: count=%d", count ); + if ( count >= m->top.options.cf_max_dangling ) + { + msg( M_INFO, "MULTI dipsa: too many dangling instances (%d) for this source IP", count ); + return false; + } + + return true; +} + /* * Get a client instance based on real address. If * the instance doesn't exist, create it while @@ -100,7 +205,8 @@ multi_get_create_instance_udp(struct multi_context *m, bool *floated) if (!m->top.c2.tls_auth_standalone || tls_pre_decrypt_lite(m->top.c2.tls_auth_standalone, &m->top.c2.from, &m->top.c2.buf)) { - if (frequency_limit_event_allowed(m->new_connection_limiter)) + if (frequency_limit_event_allowed(m->new_connection_limiter) + && dangling_instances_per_source_allowed(m, &real) ) { mi = multi_create_instance(m, &real); if (mi) diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 26f056fc..b8e28df6 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -6644,7 +6644,7 @@ add_option(struct options *options, options->real_hash_size = real; options->virtual_hash_size = real; } - else if (streq(p[0], "connect-freq") && p[1] && p[2] && !p[3]) + else if (streq(p[0], "connect-freq") && p[1] && p[2] && !p[4]) { int cf_max, cf_per; @@ -6658,6 +6658,10 @@ add_option(struct options *options, } options->cf_max = cf_max; options->cf_per = cf_per; + if ( p[3] ) + { + options->cf_max_dangling = atoi(p[3]); + } } else if (streq(p[0], "max-clients") && p[1] && !p[2]) { diff --git a/src/openvpn/options.h b/src/openvpn/options.h index e2b38939..26a78878 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -451,6 +451,7 @@ struct options bool duplicate_cn; int cf_max; int cf_per; + int cf_max_dangling; int max_clients; int max_routes_per_client; int stale_routes_check_interval;