MEDIUM: config: prevent communication with privileged ports

This commit introduces a new global setting named
harden.reject_privileged_ports.{tcp|quic}. When active, communications
with clients which use privileged source ports are forbidden. Such
behavior is considered suspicious as it can be used as spoofing or
DNS/NTP amplication attack.

Value is configured per transport protocol. For each TCP and QUIC
distinct code locations are impacted by this setting. The first one is
in sock_accept_conn() which acts as a filter for all TCP based
communications just after accept() returns a new connection. The second
one is dedicated for QUIC communication in quic_recv(). In both cases,
if a privileged source port is used and setting is disabled, received
message is silently dropped.

By default, protection are disabled for both protocols. This is to be
able to backport it without breaking changes on stable release.

This should be backported as it is an interesting security feature yet
relatively simple to implement.
This commit is contained in:
Amaury Denoyelle 2024-05-22 14:21:16 +02:00
parent 4e632545f7
commit 45f40bac4c
8 changed files with 100 additions and 0 deletions

View File

@ -1276,6 +1276,8 @@ The following keywords are supported in the "global" section :
- h1-case-adjust-file
- h2-workaround-bogus-websocket-clients
- hard-stop-after
- harden.reject-privileged-ports.tcp
- harden.reject-privileged-ports.quic
- insecure-fork-wanted
- insecure-setuid-wanted
- issuers-chain-path
@ -1944,6 +1946,12 @@ hard-stop-after <time>
See also: grace
harden.reject-privileged-ports.tcp { on | off }
harden.reject-privileged-ports.quic { on | off }
Toggle per protocol protection which forbid communication with clients which
use privileged ports as their source port. This range of ports is defined
according to RFC 6335. Protection is inactive by default on both protocols.
http-err-codes [+-]<range>[,...] [...]
Replace, reduce or extend the list of status codes that define an error as
considered by the termination codes and the "http_err_cnt" counter in stick

View File

@ -216,6 +216,8 @@ struct global {
int numa_cpu_mapping;
int thread_limit; /* hard limit on the number of threads */
int prealloc_fd;
uchar clt_privileged_ports; /* bitmask to allow client privileged ports exchanges per protocol */
/* 3-bytes hole */
int cfg_curr_line; /* line number currently being parsed */
const char *cfg_curr_file; /* config file currently being parsed or NULL */
char *cfg_curr_section; /* config section name currently being parsed or NULL */

View File

@ -138,6 +138,17 @@ struct protocol {
struct list list; /* list of registered protocols (under proto_lock) */
};
/* Transport protocol identifiers which can be used as masked values. */
enum ha_proto {
HA_PROTO_NONE = 0x00,
HA_PROTO_TCP = 0x01,
HA_PROTO_UDP = 0x02,
HA_PROTO_QUIC = 0x04,
HA_PROTO_ANY = 0xff,
};
#endif /* _HAPROXY_PROTOCOL_T_H */
/*

View File

@ -42,6 +42,7 @@
#include <haproxy/api.h>
#include <haproxy/chunk.h>
#include <haproxy/intops.h>
#include <haproxy/global.h>
#include <haproxy/namespace-t.h>
#include <haproxy/protocol-t.h>
#include <haproxy/tools-t.h>
@ -781,6 +782,21 @@ static inline int set_host_port(struct sockaddr_storage *addr, int port)
return 0;
}
/* Returns true if <addr> port is forbidden as client source using <proto>. */
static inline int port_is_restricted(const struct sockaddr_storage *addr,
enum ha_proto proto)
{
const uint16_t port = get_host_port(addr);
BUG_ON_HOT(proto != HA_PROTO_TCP && proto != HA_PROTO_QUIC);
/* RFC 6335 6. Port Number Ranges */
if (unlikely(port < 1024 && port > 0))
return !(global.clt_privileged_ports & proto);
return 0;
}
/* Convert mask from bit length form to in_addr form.
* This function never fails.
*/

View File

@ -1376,8 +1376,59 @@ static int cfg_parse_prealloc_fd(char **args, int section_type, struct proxy *cu
return 0;
}
/* Parser for harden.reject-privileged-ports.{tcp|quic}. */
static int cfg_parse_reject_privileged_ports(char **args, int section_type,
struct proxy *curpx,
const struct proxy *defpx,
const char *file, int line, char **err)
{
struct ist proto;
char onoff;
if (!*(args[1])) {
memprintf(err, "'%s' expects either 'on' or 'off'.", args[0]);
return -1;
}
proto = ist(args[0]);
while (istlen(istfind(proto, '.')))
proto = istadv(istfind(proto, '.'), 1);
if (strcmp(args[1], "on") == 0) {
onoff = 1;
}
else if (strcmp(args[1], "off") == 0) {
onoff = 0;
}
else {
memprintf(err, "'%s' expects either 'on' or 'off'.", args[0]);
return -1;
}
if (istmatch(proto, ist("tcp"))) {
if (!onoff)
global.clt_privileged_ports |= HA_PROTO_TCP;
else
global.clt_privileged_ports &= ~HA_PROTO_TCP;
}
else if (istmatch(proto, ist("quic"))) {
if (!onoff)
global.clt_privileged_ports |= HA_PROTO_QUIC;
else
global.clt_privileged_ports &= ~HA_PROTO_QUIC;
}
else {
memprintf(err, "invalid protocol for '%s'.", args[0]);
return -1;
}
return 0;
}
static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_GLOBAL, "prealloc-fd", cfg_parse_prealloc_fd },
{ CFG_GLOBAL, "harden.reject-privileged-ports.tcp", cfg_parse_reject_privileged_ports },
{ CFG_GLOBAL, "harden.reject-privileged-ports.quic", cfg_parse_reject_privileged_ports },
{ 0, NULL, NULL },
}};

View File

@ -210,6 +210,8 @@ struct global global = {
.maxsslconn = DEFAULT_MAXSSLCONN,
#endif
#endif
/* by default do not protect against clients using privileged port */
.clt_privileged_ports = HA_PROTO_ANY,
/* others NULL OK */
};

View File

@ -29,6 +29,7 @@
#include <haproxy/listener.h>
#include <haproxy/log.h>
#include <haproxy/pool.h>
#include <haproxy/protocol-t.h>
#include <haproxy/proto_quic.h>
#include <haproxy/proxy-t.h>
#include <haproxy/quic_cid.h>
@ -393,6 +394,11 @@ static ssize_t quic_recv(int fd, void *out, size_t len,
if (ret < 0)
goto end;
if (unlikely(port_is_restricted((struct sockaddr_storage *)from, HA_PROTO_QUIC))) {
ret = -1;
goto end;
}
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
switch (cmsg->cmsg_level) {
case IPPROTO_IP:

View File

@ -30,6 +30,7 @@
#include <haproxy/listener.h>
#include <haproxy/log.h>
#include <haproxy/namespace.h>
#include <haproxy/protocol-t.h>
#include <haproxy/proto_sockpair.h>
#include <haproxy/sock.h>
#include <haproxy/sock_inet.h>
@ -109,6 +110,9 @@ struct connection *sock_accept_conn(struct listener *l, int *status)
goto fail_conn;
}
if (unlikely(port_is_restricted(addr, HA_PROTO_TCP)))
goto fail_conn;
/* Perfect, the connection was accepted */
conn = conn_new(&l->obj_type);
if (!conn)