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:
parent
4e632545f7
commit
45f40bac4c
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
||||
/*
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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 },
|
||||
}};
|
||||
|
||||
|
@ -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 */
|
||||
};
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user