freeradius/freeradius-blastradius-fix.patch
Antonio Torres b3ac7a5c16
Backport BlastRADIUS CVE fix
Manual backport from v3.0.x branch.

Resolves: RHEL-46572
Signed-off-by: Antonio Torres <antorres@redhat.com>
2024-07-11 15:34:56 +02:00

1503 lines
56 KiB
Diff

From: Antonio Torres <antorres@redhat.com>
Date: Thu, 11 Jul, 2024
Subject: Backport fix for BlastRADIUS
Manual backport from v3.0.x branch, commit range 3a00a6ecc188629b0441fd45ad61ca8986de156e..da643f1edc267ce95260dc36069e6f1a7a4d66f8
Resolves: RHEL-46572
Signed-off-by: Antonio Torres <antorres@redhat.com>
---
man/man1/radclient.1 | 10 ++-
man/man1/radtest.1 | 11 ++-
raddb/clients.conf | 47 +++++++++---
raddb/proxy.conf | 19 +++++
raddb/radiusd.conf.in | 185 ++++++++++++++++++++++++++++++++++++++++++++++++
src/include/clients.h | 6 +-
src/include/conffile.h | 1 +
src/include/libradius.h | 19 ++++-
src/include/radius.h | 1 +
src/include/radiusd.h | 6 ++
src/include/realms.h | 1 +
src/lib/radius.c | 87 ++++++++++++++++++++---
src/main/client.c | 45 ++++++++++--
src/main/conffile.c | 4 +-
src/main/listen.c | 141 +++++++++++++++++++++++++++++++++++-
src/main/mainconfig.c | 70 ++++++++++++++++++
src/main/process.c | 65 +++++++++++++++++
src/main/radclient.c | 147 +++++++++++++++++++++++++++++++++++++-
src/main/radtest.in | 6 +-
src/main/realms.c | 11 +++
src/main/tls_listen.c | 5 ++
21 files changed, 855 insertions(+), 32 deletions(-)
diff --git a/man/man1/radclient.1 b/man/man1/radclient.1
index 229dcae0c7..b83bee931a 100644
--- a/man/man1/radclient.1
+++ b/man/man1/radclient.1
@@ -1,10 +1,11 @@
-.TH RADCLIENT 1 "22 March 2019" "" "FreeRADIUS Daemon"
+.TH RADCLIENT 1 "21 May 2024" "" "FreeRADIUS Daemon"
.SH NAME
radclient - send packets to a RADIUS server, show reply
.SH SYNOPSIS
.B radclient
.RB [ \-4 ]
.RB [ \-6 ]
+.RB [ \-b ]
.RB [ \-c
.IR count ]
.RB [ \-d
@@ -52,6 +53,13 @@ automatically encrypted before the packet is sent to the server.
Use IPv4 (default)
.IP \-6
Use IPv6
+.IP \-b
+Enforce the Blast RADIUS checks. All replies to an Access-Request packet
+must contain a Message-Authenticator as the first attribute.
+
+For compatibility with old servers, this flag is not set by default.
+However, radclient still checks for the Blast RADIUS signature, and
+discards packets which match the attack.
.IP \-c\ \fIcount\fP
Send each packet \fIcount\fP times.
.IP \-d\ \fIraddb_directory\fP
diff --git a/man/man1/radtest.1 b/man/man1/radtest.1
index b3184779c0..db32e7f68d 100644
--- a/man/man1/radtest.1
+++ b/man/man1/radtest.1
@@ -1,4 +1,4 @@
-.TH RADTEST 1 "5 April 2010" "" "FreeRADIUS Daemon"
+.TH RADTEST 1 "21 May 2024" "" "FreeRADIUS Daemon"
.SH NAME
radtest - send packets to a RADIUS server, show reply
.SH SYNOPSIS
@@ -15,6 +15,8 @@ radtest - send packets to a RADIUS server, show reply
.IR ]
.RB [ \-6
.IR ]
+.RB [ \-b
+.IR ]
.I user password radius-server nas-port-number secret
.RB [ ppphint ]
.RB [ nasname ]
@@ -26,6 +28,13 @@ way to test a radius server.
.SH OPTIONS
+.IP \-b
+Enforce the Blast RADIUS checks. All replies to an Access-Request packet
+must contain a Message-Authenticator as the first attribute.
+
+For compatibility with old servers, this flag is not set by default.
+However, radclient still checks for the Blast RADIUS signature, and
+discards packets which match the attack.
.IP "\-d \fIraddb_directory\fP"
The directory that contains the RADIUS dictionary files. Defaults to
\fI/etc/raddb\fP.
diff --git a/raddb/clients.conf b/raddb/clients.conf
index 76b300d3c5..d55414b7d2 100644
--- a/raddb/clients.conf
+++ b/raddb/clients.conf
@@ -100,15 +100,44 @@ client localhost {
secret = testing123
#
- # Old-style clients do not send a Message-Authenticator
- # in an Access-Request. RFC 5080 suggests that all clients
- # SHOULD include it in an Access-Request. The configuration
- # item below allows the server to require it. If a client
- # is required to include a Message-Authenticator and it does
- # not, then the packet will be silently discarded.
- #
- # allowed values: yes, no
- require_message_authenticator = no
+ # The global configuration "security.require_message_authenticator"
+ # flag sets the default for all clients. That default can be
+ # over-ridden here, by setting it to a value. If no value is set,
+ # then the default from the "radiusd.conf" file is used.
+ #
+ # See that file for full documentation on the flag, along
+ # with allowed values and meanings.
+ #
+ # This flag exists solely for legacy clients which do not send
+ # Message-Authenticator in all Access-Request packets. We do not
+ # recommend setting it to "no".
+ #
+ # The number one way to protect yourself from the BlastRADIUS
+ # attack is to update all RADIUS servers, and then set this
+ # flag to "yes". If all RADIUS servers are updated, and if
+ # all of them have this flag set to "yes" for all clients,
+ # then your network is safe. You can then upgrade the
+ # clients when it is convenient, instead of rushing the
+ # upgrades.
+ #
+ # allowed values: yes, no, auto
+# require_message_authenticator = no
+
+ #
+ # The global configuration "security.limit_proxy_state"
+ # flag sets the default for all clients. That default can be
+ # over-ridden here, by setting it to "no".
+ #
+ # See that file for full documentation on the flag, along
+ # with allowed values,and meanings.
+ #
+ # This flag exists solely for legacy clients which do not send
+ # Message-Authenticator in all Access-Request packets. We do not
+ # recommend setting it to "no".
+ #
+ # allowed values: yes, no, auto
+ #
+# limit_proxy_state = yes
#
# The short name is used as an alias for the fully qualified
diff --git a/raddb/proxy.conf b/raddb/proxy.conf
index 91b4b37930..fa362b8a74 100644
--- a/raddb/proxy.conf
+++ b/raddb/proxy.conf
@@ -204,6 +204,25 @@ home_server localhost {
#
secret = testing123
+
+ #
+ # The global configuration "security.require_message_authenticator"
+ # flag sets the default for all home servers. That default can be
+ # over-ridden here, by setting it to a value. If no value is set,
+ # then the default from the "radiusd.conf" file is used.
+ #
+ # See that file for full documentation on the flag, along
+ # with allowed values and meanings.
+ #
+ # This flag exists solely for legacy home servers which do
+ # not send Message-Authenticator in all Access-Accept,
+ # Access-Reject, or Access-Challenge packets. We do not
+ # recommend setting it to "no".
+ #
+ # allowed values: yes, no, auto
+ #
+# require_message_authenticator = no
+
############################################################
#
# The rest of the configuration items listed here are optional,
diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in
index e8aee3c001..6fa9ded0f9 100644
--- a/raddb/radiusd.conf.in
+++ b/raddb/radiusd.conf.in
@@ -564,6 +564,191 @@ security {
#
status_server = yes
+ #
+ # Global configuration for requiring Message-Authenticator in
+ # all Access-* packets sent over UDP or TCP. This flag is
+ # ignored for TLS.
+ #
+ # The number one way to protect yourself from the BlastRADIUS
+ # attack is to update all RADIUS servers, and then set this
+ # flag to "yes". If all RADIUS servers are updated, and if
+ # all of them have this flag set to "yes" for all clients,
+ # then your network is safe. You can then upgrade the
+ # clients when it is convenient, instead of rushing the
+ # upgrades.
+ #
+ # This flag sets the global default for all clients and home
+ # servers. It can be over-ridden in an individual client or
+# home_server definition by adding the same flag to that
+ # section with an appropriate value.
+ #
+ # All upgraded RADIUS implementations should send
+ # Message-Authenticator in all Access-Request, Access-Accept,
+ # Access-Reject, and Access-Challenge packets. Once all
+ # systems are upgraded, setting this flag to "yes" is the
+ # best protection from the attack.
+ #
+ # The possible values and meanings for
+ # "require_message_authenticator" are;
+ #
+ # * "no" - allow Access-* packet which do not contain
+ # Message-Authenticator
+ #
+ # For a client, if this flag is set to "no", then the
+ # "limit_proxy_state" flag, below, is also checked.
+ #
+ # For a home_server, if this flag is set to "no", then the
+ # Access-Accept, Access-Reject, and Access-Challenge
+ # packets do not need to contain Message-Authenticator.
+ #
+ # The only reason to set this flag to "no" is when the
+ # RADIUS client or home server has not been updated. It is
+ # always safer to set this flag "no" in the individual
+ # client or home_server definition. The global flag SHOULD
+ # still be set to a safe value: "yes".
+ #
+ # WARNING: Setting this flag and the "limit_proxy_state"
+ # flag to "no" will allow MITM attackers to create fake
+ # Access-Accept packets to the NAS! At least one of them
+ # MUST be set to "yes" for the system to have any
+ # protection against the attack.
+ #
+ # * "yes" - Require that all Access-* packets (client and
+ # home_server) contain Message-Authenticator. If a packet
+ # does not contain Message-Authenticator, then it is
+ # discarded.
+ #
+ # * "auto" - Automatically determine the value of the flag,
+ # based on the first packet received from that client or
+ # home_server.
+ #
+ # If the packet does not contain Message-Authenticator,
+ # then the value of the flag is automatically switched to
+ # "no".
+ #
+ # If the packet contains Message-Authenticator but not
+ # EAP-Message, then the value of the flag is automatically
+ # switched to "yes". The server has to check for
+ # EAP-Message, because the previous RFCs require that the
+ # packet contains Message-Authenticator when it also
+ # contains EAP-Message. So having a Message-Authenticator
+ # in those packets doesn't give the server enough
+ # information to determined if the client or home_server
+ # has been updated.
+ #
+ # If the packet contains Message-Authenticator and
+ # EAP-Message, then the flag is left at the "auto" value.
+ #
+ # WARNING: This switch is done for the first packet
+ # received from that client or home server. The change
+ # does NOT persist across server restarts. You MUST change
+ # the to "yes" manually, in order to make a permanent
+ # change to the configuration.
+ #
+ # WARNING: If there are multiple NASes with the same source
+ # IP and client definitions, BUT the NASes have different
+ # behavior, then this flag WILL LIKELY BREAK YOUR NETWORK.
+ #
+ # That is, when there are multiple different RADIUS clients
+ # behind one NATed IP address, then these security settings
+ # have to be set to allow the MOST INSECURE packets to be
+ # processed. This is a terrible idea, and will leave your
+ # network vulnerable to the attack. Please upgrade all
+ # clients immediately.
+ #
+ # The only solution to that rare configuration is to set
+ # this flag to "no", in which case the network will work,
+ # but will be vulnerable to the attack.
+ #
+ require_message_authenticator = auto
+
+ #
+ # Global configuration for limiting the combination of
+ # Proxy-State and Message-Authenticator. This flag only
+ # applies to packets sent over UDP or TCP. This flag is
+ # ignored for TLS.
+ #
+ # This flag sets the global default for all clients. It can
+ # be over-ridden in an individual client definition by adding
+ # the same flag to that section with an appropriate value.
+ #
+ # If "require_message_authenticator" is set to "yes", this
+ # configuration item is ignored.
+ #
+ # If "require_message_authenticator" is set to "no", this
+ # configuration item is checked.
+ #
+# The possible values and meanings for "limit_proxy_state" are;
+ #
+ # * "no" - allow any packets from the client, even packets
+ # which contain the BlastRADIUS attack. Please be aware
+ # that in this configuration the server will complain for
+ # EVERY packet which it receives.
+ #
+ # The only reason to set this flag to "no" is when the
+ # client is a proxy, AND the proxy does not send
+ # Message-Authenticator in Access-Request packets. Even
+ # then, the best approach to fix the issue is to (1) update
+ # the proxy to send Message-Authenticator, and if that
+ # can't be done, then (2) set this flag to "no", but ONLY
+ # for that one client. The global flag SHOULD still be set
+ # to a safe value: "yes".
+ #
+ # WARNING: Setting both this flag and the
+ # "require_message_authenticator" flag to "no" will allow
+ # MITM attackers to create fake Access-Accept packets to the
+ # NAS! At least one of them MUST be set to "yes" for the
+ # system to have any protection against the attack.
+ #
+ # * "yes" - Allow packets without Message-Authenticator,
+ # but only when they do not contain Proxy-State.
+ # packets which contain Proxy-State MUST also contain
+ # Message-Authenticator, otherwise they are discarded.
+ #
+ # This setting is safe for most NASes, GGSNs, BRAS, etc.
+ # Most regular RADIUS clients do not send Proxy-State
+ # attributes for Access-Request packets that they originate.
+ # However some aggregators (e.g. Wireless LAN Controllers)
+ # may act as a RADIUS proxy for requests from their cohort
+ # of managed devices, and in such cases will provide a
+ # Proxy-State attribute. For those systems, you _must_ look
+ # at the actual packets to determine what to do. It may be
+ # that the only way to fix the vulnerability is to upgrade
+ # the WLC, and set "require_message_authenticator" to "yes".
+ #
+ # * "auto" - Automatically determine the value of the flag,
+ # based on the first packet received from that client.
+ #
+ # If the packet contains Proxy-State but no
+ # Message-Authenticator, then the value of the flag is
+ # automatically switched to "no".
+ #
+ # For all other situations, the value of the flag is
+ # automatically switched to "yes".
+ #
+ # WARNING: This switch is done for the first packet
+ # received from that client. The change does NOT persist
+ # across server restarts. You MUST change the to "yes"
+ # manually, in order to make a permanent change to the
+ # configuration.
+ #
+ # WARNING: If there are multiple NASes with the same source
+ # IP and client definitions, BUT the NASes have different
+ # behavior, then this flag WILL LIKELY BREAK YOUR NETWORK.
+ #
+ # That is, when there are multiple different RADIUS clients
+ # behind one NATed IP address, then these security settings
+ # have to be set to allow the MOST INSECURE packets to be
+ # processed. This is a terrible idea, and will leave your
+ # network vulnerable to the attack. Please upgrade all
+ # clients immediately.
+ #
+ # The only solution to that rare configuration is to set
+ # this flag to "no", in which case the network will work,
+ # but will be vulnerable to the attack.
+ #
+ limit_proxy_state = auto
+
@openssl_version_check_config@
}
diff --git a/src/include/clients.h b/src/include/clients.h
index 560211557f..2cde3a2e37 100644
--- a/src/include/clients.h
+++ b/src/include/clients.h
@@ -39,7 +39,11 @@ typedef struct radclient {
char const *secret; //!< Secret PSK.
- bool message_authenticator; //!< Require RADIUS message authenticator in requests.
+ fr_bool_auto_t require_ma; //!< Require RADIUS message authenticator in requests.
+
+ bool dynamic_require_ma; //!< for dynamic clients
+
+ fr_bool_auto_t limit_proxy_state; //!< Limit Proxy-State in requests
char const *nas_type; //!< Type of client (arbitrary).
diff --git a/src/include/conffile.h b/src/include/conffile.h
index 8cb045c946..ddbcae4e4f 100644
--- a/src/include/conffile.h
+++ b/src/include/conffile.h
@@ -140,6 +140,7 @@ typedef struct timeval _timeval_t;
#define PW_TYPE_MULTI (1 << 18) //!< CONF_PAIR can have multiple copies.
#define PW_TYPE_NOT_EMPTY (1 << 19) //!< CONF_PAIR is required to have a non zero length value.
#define PW_TYPE_FILE_EXISTS ((1 << 20) | PW_TYPE_STRING) //!< File matching value must exist
+#define PW_TYPE_IGNORE_DEFAULT (1 << 21) //!< don't set from .dflt if the CONF_PAIR is missing
/* @} **/
#define FR_INTEGER_COND_CHECK(_name, _var, _cond, _new)\
diff --git a/src/include/libradius.h b/src/include/libradius.h
index ce2f713de1..2efef8b1d3 100644
--- a/src/include/libradius.h
+++ b/src/include/libradius.h
@@ -402,6 +402,10 @@ typedef struct radius_packet {
size_t partial;
int proto;
#endif
+ bool tls; //!< uses secure transport
+ bool message_authenticator;
+ bool proxy_state;
+ bool eap_message;
} RADIUS_PACKET;
typedef enum {
@@ -507,6 +511,13 @@ DICT_VENDOR *dict_vendorbyvalue(int vendor);
/* radius.c */
int rad_send(RADIUS_PACKET *, RADIUS_PACKET const *, char const *secret);
bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason);
+
+/*
+ * 1 == require_ma
+ * 2 == msg_peek
+ * 4 == limit_proxy_state
+ * 8 == require_ma for Access-* replies and Protocol-Error
+ */
RADIUS_PACKET *rad_recv(TALLOC_CTX *ctx, int fd, int flags);
ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port, int *code);
void rad_recv_discard(int sockfd);
@@ -694,7 +705,7 @@ extern bool fr_dns_lookups; /* do IP -> hostname lookups? */
extern bool fr_hostname_lookups; /* do hostname -> IP lookups? */
extern int fr_debug_lvl; /* 0 = no debugging information */
extern uint32_t fr_max_attributes; /* per incoming packet */
-#define FR_MAX_PACKET_CODE (52)
+#define FR_MAX_PACKET_CODE (53)
extern char const *fr_packet_codes[FR_MAX_PACKET_CODE];
#define is_radius_code(_x) ((_x > 0) && (_x < FR_MAX_PACKET_CODE))
extern FILE *fr_log_fp;
@@ -932,6 +943,12 @@ int fr_socket_wait_for_connect(int sockfd, struct timeval *timeout);
}
#endif
+typedef enum {
+ FR_BOOL_FALSE = 0,
+ FR_BOOL_TRUE,
+ FR_BOOL_AUTO,
+} fr_bool_auto_t;
+
#include <freeradius-devel/packet.h>
#ifdef WITH_TCP
diff --git a/src/include/radius.h b/src/include/radius.h
index 473528d65d..147d674eed 100644
--- a/src/include/radius.h
+++ b/src/include/radius.h
@@ -61,6 +61,7 @@ typedef enum {
PW_CODE_COA_REQUEST = 43, //!< RFC3575/RFC5176 - CoA-Request
PW_CODE_COA_ACK = 44, //!< RFC3575/RFC5176 - CoA-Ack (positive)
PW_CODE_COA_NAK = 45, //!< RFC3575/RFC5176 - CoA-Nak (not willing to perform)
+ PW_CODE_PROTOCOL_ERROR = 52, //!< RFC7930 - Protocol layer issue
PW_CODE_MAX = 255, //!< Maximum possible code
} PW_CODE;
diff --git a/src/include/radiusd.h b/src/include/radiusd.h
index 0af3f51458..bd0aa668d0 100644
--- a/src/include/radiusd.h
+++ b/src/include/radiusd.h
@@ -171,6 +171,10 @@ typedef struct main_config {
bool exiting; //!< are we exiting?
+ fr_bool_auto_t require_ma; //!< global configuration for all clients and home servers
+
+ fr_bool_auto_t limit_proxy_state; //!< global configuration for all clients
+
#ifdef ENABLE_OPENSSL_VERSION_CHECK
char const *allow_vulnerable_openssl; //!< The CVE number of the last security issue acknowledged.
@@ -557,6 +561,8 @@ int main_config_free(void);
void main_config_hup(void);
void hup_logfile(void);
+int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str);
+
/* listen.c */
void listen_free(rad_listen_t **head);
int listen_init(CONF_SECTION *cs, rad_listen_t **head, bool spawn_flag);
diff --git a/src/include/realms.h b/src/include/realms.h
index 6dae8b4f85..ea5665c56c 100644
--- a/src/include/realms.h
+++ b/src/include/realms.h
@@ -59,6 +59,7 @@ typedef struct home_server {
//!< stats or when specifying home servers for a pool.
bool dual; //!< One of a pair of homeservers on consecutive ports.
+ fr_bool_auto_t require_ma; //!< for all replies to Access-Request and Status-Server
char const *server; //!< For internal proxying
char const *parent_server;
diff --git a/src/lib/radius.c b/src/lib/radius.c
index 3881111f7d..556f3b961e 100644
--- a/src/lib/radius.c
+++ b/src/lib/radius.c
@@ -142,8 +142,9 @@ char const *fr_packet_codes[FR_MAX_PACKET_CODE] = {
"47",
"48",
"49",
- "IP-Address-Allocate",
- "IP-Address-Release", //!< 50
+ "IP-Address-Allocate", //!< 50
+ "IP-Address-Release",
+ "Protocol-Error",
};
@@ -1700,6 +1701,15 @@ int rad_vp2attr(RADIUS_PACKET const *packet, RADIUS_PACKET const *original,
return rad_vp2vsa(packet, original, secret, pvp, start, room);
}
+static const bool code2ma[FR_MAX_PACKET_CODE] = {
+ [ PW_CODE_ACCESS_REQUEST ] = true,
+ [ PW_CODE_ACCESS_ACCEPT ] = true,
+ [ PW_CODE_ACCESS_REJECT ] = true,
+ [ PW_CODE_ACCESS_CHALLENGE ] = true,
+ [ PW_CODE_STATUS_SERVER ] = true,
+ [ PW_CODE_PROTOCOL_ERROR ] = true,
+};
+
/** Encode a packet
*
@@ -1712,6 +1722,7 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
uint16_t total_length;
int len;
VALUE_PAIR const *reply;
+ bool seen_ma = false;
/*
* A 4K packet, aligned on 64-bits.
@@ -1775,6 +1786,27 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
* memcpy.
*/
+ /*
+ * Always add Message-Authenticator for replies to
+ * Access-Request packets, and for all Access-Accept,
+ * Access-Reject, Access-Challenge.
+ *
+ * It must be the FIRST attribute in the packet.
+ */
+ if (!packet->tls &&
+ ((code2ma[packet->code]) || (original && code2ma[original->code]))) {
+ seen_ma = true;
+
+ packet->offset = RADIUS_HDR_LEN;
+
+ ptr[0] = PW_MESSAGE_AUTHENTICATOR;
+ ptr[1] = 18;
+ memset(ptr + 2, 0, 16);
+
+ ptr += 18;
+ total_length += 18;
+ }
+
/*
* Loop over the reply attributes for the packet.
*/
@@ -1832,6 +1864,13 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
* length and initial value.
*/
if (!reply->da->vendor && (reply->da->attr == PW_MESSAGE_AUTHENTICATOR)) {
+ /*
+ * We have already encoded the Message-Authenticator, don't do it again.
+ */
+ if (seen_ma) {
+ reply = reply->next;
+ continue;
+ }
if (room < 18) break;
/*
@@ -2323,6 +2362,8 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
radius_packet_t *hdr;
char host_ipaddr[128];
bool require_ma = false;
+ bool limit_proxy_state = false;
+ bool seen_proxy_state = false;
bool seen_ma = false;
uint32_t num_attributes;
decode_fail_t failure = DECODE_FAIL_NONE;
@@ -2371,15 +2412,26 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
}
/*
- * Message-Authenticator is required in Status-Server
- * packets, otherwise they can be trivially forged.
+ * If the caller requires Message-Authenticator, then set
+ * the flag.
*/
- if (hdr->code == PW_CODE_STATUS_SERVER) require_ma = true;
/*
- * It's also required if the caller asks for it.
+ * We also require Message-Authenticator if the packet
+ * code is Status-Server.
+ *
+ * If we're receiving packets from a proxy socket, then
+ * require Message-Authenticator for Access-* replies,
+ * and for Protocol-Error.
*/
- if (flags) require_ma = true;
+ require_ma = ((flags & 0x01) != 0) || (hdr->code == PW_CODE_STATUS_SERVER) || (((flags & 0x08) != 0) && code2ma[hdr->code]);
+
+ /*
+ *
+ * We only limit Proxy-State if we're not requiring
+ * Message-Authenticator.
+ */
+ limit_proxy_state = ((flags & 0x04) != 0) && !require_ma;
/*
* Repeat the length checks. This time, instead of
@@ -2534,6 +2586,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
case PW_EAP_MESSAGE:
require_ma = true;
eap = true;
+ packet->eap_message = true;
break;
case PW_USER_PASSWORD:
@@ -2542,6 +2595,11 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
non_eap = true;
break;
+ case PW_PROXY_STATE:
+ seen_proxy_state = true;
+ packet->proxy_state = true;
+ break;
+
case PW_MESSAGE_AUTHENTICATOR:
if (attr[1] != 2 + AUTH_VECTOR_LEN) {
FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: Message-Authenticator has invalid length %d",
@@ -2553,6 +2611,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
goto finish;
}
seen_ma = true;
+ packet->message_authenticator = true;
break;
}
@@ -2609,7 +2668,19 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
* Message-Authenticator attributes.
*/
if (require_ma && !seen_ma) {
- FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute",
+ FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute. You may need to set \"require_message_authenticator = no\" in the configuration.",
+ inet_ntop(packet->src_ipaddr.af,
+ &packet->src_ipaddr.ipaddr,
+ host_ipaddr, sizeof(host_ipaddr)));
+ failure = DECODE_FAIL_MA_MISSING;
+ goto finish;
+ }
+
+ /*
+ * The client is a NAS which shouldn't send Proxy-State, but it did!
+ */
+ if (limit_proxy_state && seen_proxy_state && !seen_ma) {
+ FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute, but still has one or more Proxy-State attributes",
inet_ntop(packet->src_ipaddr.af,
&packet->src_ipaddr.ipaddr,
host_ipaddr, sizeof(host_ipaddr)));
diff --git a/src/main/client.c b/src/main/client.c
index abcc15d225..af5b122af5 100644
--- a/src/main/client.c
+++ b/src/main/client.c
@@ -279,7 +279,8 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client)
(old->coa_server == client->coa_server) &&
(old->coa_pool == client->coa_pool) &&
#endif
- (old->message_authenticator == client->message_authenticator)) {
+ (old->require_ma == client->require_ma) &&
+ (old->limit_proxy_state == client->limit_proxy_state)) {
WARN("Ignoring duplicate client %s", client->longname);
client_free(client);
return true;
@@ -443,6 +444,8 @@ static fr_ipaddr_t cl_ipaddr;
static uint32_t cl_netmask;
static char const *cl_srcipaddr = NULL;
static char const *hs_proto = NULL;
+static char const *require_message_authenticator = NULL;
+static char const *limit_proxy_state = NULL;
#ifdef WITH_TCP
static CONF_PARSER limit_config[] = {
@@ -465,7 +468,8 @@ static const CONF_PARSER client_config[] = {
{ "src_ipaddr", FR_CONF_POINTER(PW_TYPE_STRING, &cl_srcipaddr), NULL },
- { "require_message_authenticator", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), "no" },
+ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL },
+ { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &limit_proxy_state), NULL },
{ "secret", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, RADCLIENT, secret), NULL },
{ "shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), NULL },
@@ -661,7 +665,7 @@ static const CONF_PARSER dynamic_config[] = {
{ "FreeRADIUS-Client-Src-IP-Address", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, RADCLIENT, src_ipaddr), NULL },
{ "FreeRADIUS-Client-Src-IPv6-Address", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, RADCLIENT, src_ipaddr), NULL },
- { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), NULL },
+ { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, dynamic_require_ma), NULL },
{ "FreeRADIUS-Client-Secret", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, secret), "" },
{ "FreeRADIUS-Client-Shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), "" },
@@ -843,8 +847,19 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo
c = talloc_zero(ctx, RADCLIENT);
c->cs = cs;
+ /*
+ * Set the "require message authenticator" and "limit
+ * proxy state" flags from the global default. If the
+ * configuration item exists, AND is set, it will
+ * over-ride the flag.
+ */
+ c->require_ma = main_config.require_ma;
+ c->limit_proxy_state = main_config.limit_proxy_state;
+
memset(&cl_ipaddr, 0, sizeof(cl_ipaddr));
cl_netmask = 255;
+ require_message_authenticator = NULL;
+ limit_proxy_state = NULL;
if (cf_section_parse(cs, c, client_config) < 0) {
cf_log_err_cs(cs, "Error parsing client section");
@@ -855,6 +870,9 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo
cl_srcipaddr = NULL;
#endif
+ require_message_authenticator = NULL;
+ limit_proxy_state = NULL;
+
return NULL;
}
@@ -1112,6 +1130,16 @@ done_coa:
}
#endif
+ if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &c->require_ma, require_message_authenticator) < 0) {
+ goto error;
+ }
+
+ if (c->require_ma != FR_BOOL_TRUE) {
+ if (fr_bool_auto_parse(cf_pair_find(cs, "limit_proxy_state"), &c->limit_proxy_state, limit_proxy_state) < 0) {
+ goto error;
+ }
+ }
+
return c;
}
@@ -1156,7 +1184,7 @@ RADCLIENT *client_afrom_query(TALLOC_CTX *ctx, char const *identifier, char cons
if (shortname) c->shortname = talloc_typed_strdup(c, shortname);
if (type) c->nas_type = talloc_typed_strdup(c, type);
if (server) c->server = talloc_typed_strdup(c, server);
- c->message_authenticator = require_ma;
+ c->require_ma = require_ma;
return c;
}
@@ -1342,10 +1370,10 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request)
*pi = vp->vp_integer;
/*
- * Same nastiness as above.
+ * Same nastiness as above, but hard-coded for require Message-Authenticator.
*/
for (parse = client_config; parse->name; parse++) {
- if (parse->offset == dynamic_config[i].offset) break;
+ if (parse->type == PW_TYPE_BOOLEAN) break;
}
if (!parse) break;
@@ -1434,6 +1462,11 @@ validate:
goto error;
}
+ /*
+ * It can't be set to "auto". Too bad.
+ */
+ c->require_ma = (fr_bool_auto_t) c->dynamic_require_ma;
+
if (!client_add_dynamic(clients, request->client, c)) {
return NULL;
}
diff --git a/src/main/conffile.c b/src/main/conffile.c
index 6d6441a55b..0e1ced2385 100644
--- a/src/main/conffile.c
+++ b/src/main/conffile.c
@@ -1417,6 +1417,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d
{
int rcode;
bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, multi, file_exists;
+ bool ignore_dflt;
char **q;
char const *value;
CONF_PAIR *cp = NULL;
@@ -1440,6 +1441,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d
cant_be_empty = (type & PW_TYPE_NOT_EMPTY);
tmpl = (type & PW_TYPE_TMPL);
multi = (type & PW_TYPE_MULTI);
+ ignore_dflt = (type & PW_TYPE_IGNORE_DEFAULT);
if (attribute) required = true;
if (required) cant_be_empty = true; /* May want to review this in the future... */
@@ -1463,7 +1465,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d
* section, use the default value.
*/
if (!cp) {
- if (deprecated) return 0; /* Don't set the default value */
+ if (deprecated || ignore_dflt) return 0; /* Don't set the default value */
rcode = 1;
value = dflt;
diff --git a/src/main/listen.c b/src/main/listen.c
index ebf7f5221c..0d178138c9 100644
--- a/src/main/listen.c
+++ b/src/main/listen.c
@@ -456,6 +456,122 @@ int rad_status_server(REQUEST *request)
return 0;
}
+static void blastradius_checks(RADIUS_PACKET *packet, RADCLIENT *client)
+{
+ if (client->require_ma == FR_BOOL_TRUE) return;
+
+ if (client->require_ma == FR_BOOL_AUTO) {
+ if (!packet->message_authenticator) {
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ ERROR("BlastRADIUS check: Received packet without Message-Authenticator.");
+ ERROR("Setting \"require_message_authenticator = false\" for client %s", client->shortname);
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK.");
+ ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname);
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ client->require_ma = FR_BOOL_FALSE;
+
+ /*
+ * And fall through to the
+ * limit_proxy_state checks, which might
+ * complain again. Oh well, maybe that
+ * will make people read the messages.
+ */
+
+ } else if (packet->eap_message) {
+ /*
+ * Don't set it to "true" for packets
+ * with EAP-Message. It's already
+ * required there, and we might get a
+ * non-EAP packet with (or without)
+ * Message-Authenticator
+ */
+ return;
+ } else {
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ ERROR("BlastRADIUS check: Received packet with Message-Authenticator.");
+ ERROR("Setting \"require_message_authenticator = true\" for client %s", client->shortname);
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ ERROR("It looks like the client has been updated to protect from the BlastRADIUS attack.");
+ ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname);
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+
+ client->require_ma = FR_BOOL_TRUE;
+ return;
+ }
+
+ }
+
+ /*
+ * If all of the checks are turned off, then complain for every packet we receive.
+ */
+ if (client->limit_proxy_state == FR_BOOL_FALSE) {
+ /*
+ * We have a Message-Authenticator, and it's valid. We don't need to compain.
+ */
+ if (!fr_debug_lvl) return; /* easier than checking for each line below */
+
+ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ DEBUG("BlastRADIUS check: Received packet without Message-Authenticator.");
+ DEBUG("YOU MUST SET \"require_message_authenticator = true\", or");
+ DEBUG("YOU MUST SET \"limit_proxy_state = true\" for client %s", client->shortname);
+ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ DEBUG("The packet does not contain Message-Authenticator, which is a security issue");
+ DEBUG("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK.");
+ DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname);
+ DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ return;
+ }
+
+ /*
+ * Don't complain here. rad_packet_ok() will instead
+ * complain about every packet with Proxy-State but which
+ * is missing Message-Authenticator.
+ */
+ if (client->limit_proxy_state == FR_BOOL_TRUE) {
+ return;
+ }
+
+ if (packet->proxy_state && !packet->message_authenticator) {
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ ERROR("BlastRADIUS check: Received packet with Proxy-State, but without Message-Authenticator.");
+ ERROR("This is either a BlastRADIUS attack, OR");
+ ERROR("the client is a proxy RADIUS server which has not been upgraded.");
+ ERROR("Setting \"limit_proxy_state = false\" for client %s", client->shortname);
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK.");
+ DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname);
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+
+ client->limit_proxy_state = FR_BOOL_FALSE;
+
+ } else {
+ client->limit_proxy_state = FR_BOOL_TRUE;
+
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ if (!packet->proxy_state) {
+ ERROR("BlastRADIUS check: Received packet without Proxy-State.");
+ } else {
+ ERROR("BlastRADIUS check: Received packet with Proxy-State and Message-Authenticator.");
+ }
+
+ ERROR("Setting \"limit_proxy_state = true\" for client %s", client->shortname);
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+
+ if (!packet->message_authenticator) {
+ ERROR("The packet does not contain Message-Authenticator, which is a security issue.");
+ ERROR("UPGRADE THE CLIENT AS YOUR NETWORK MAY BE VULNERABLE TO THE BLASTRADIUS ATTACK.");
+ DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname);
+ } else {
+ ERROR("The packet contains Message-Authenticator.");
+ if (!packet->eap_message) ERROR("The client has likely been upgraded to protect from the attack.");
+ ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname);
+ }
+ ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ }
+}
+
+
#ifdef WITH_TCP
static int dual_tcp_recv(rad_listen_t *listener)
{
@@ -532,6 +648,21 @@ static int dual_tcp_recv(rad_listen_t *listener)
switch (packet->code) {
case PW_CODE_ACCESS_REQUEST:
if (listener->type != RAD_LISTEN_AUTH) goto bad_packet;
+
+ /*
+ * Enforce BlastRADIUS checks on TCP, too.
+ */
+ if (!rad_packet_ok(packet, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2), NULL)) {
+ FR_STATS_INC(auth, total_malformed_requests);
+ rad_free(&sock->packet);
+ return 0;
+ }
+
+ /*
+ * Perform BlastRADIUS checks and warnings.
+ */
+ if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client);
+
FR_STATS_INC(auth, total_requests);
fun = rad_authenticate;
break;
@@ -1562,7 +1693,7 @@ static int auth_socket_recv(rad_listen_t *listener)
* Now that we've sanity checked everything, receive the
* packet.
*/
- packet = rad_recv(ctx, listener->fd, client->message_authenticator);
+ packet = rad_recv(ctx, listener->fd, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2));
if (!packet) {
FR_STATS_INC(auth, total_malformed_requests);
if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
@@ -1570,6 +1701,12 @@ static int auth_socket_recv(rad_listen_t *listener)
return 0;
}
+
+ /*
+ * Perform BlastRADIUS checks and warnings.
+ */
+ if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client);
+
#ifdef __APPLE__
#ifdef WITH_UDPFROMTO
/*
@@ -1955,7 +2092,7 @@ static int coa_socket_recv(rad_listen_t *listener)
* Now that we've sanity checked everything, receive the
* packet.
*/
- packet = rad_recv(ctx, listener->fd, client->message_authenticator);
+ packet = rad_recv(ctx, listener->fd, client->require_ma | (((int) client->limit_proxy_state) << 2));
if (!packet) {
FR_STATS_INC(coa, total_malformed_requests);
if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c
index e9dd412dee..854c7155aa 100644
--- a/src/main/mainconfig.c
+++ b/src/main/mainconfig.c
@@ -73,6 +73,8 @@ static char const *gid_name = NULL;
static char const *chroot_dir = NULL;
static bool allow_core_dumps = false;
static char const *radlog_dest = NULL;
+static char const *require_message_authenticator = NULL;
+static char const *limit_proxy_state = NULL;
/*
* These are not used anywhere else..
@@ -87,6 +89,53 @@ static bool do_colourise = false;
static char const *radius_dir = NULL; //!< Path to raddb directory
+static const FR_NAME_NUMBER fr_bool_auto_names[] = {
+ { "false", FR_BOOL_FALSE },
+ { "no", FR_BOOL_FALSE },
+ { "0", FR_BOOL_FALSE },
+
+ { "true", FR_BOOL_TRUE },
+ { "yes", FR_BOOL_TRUE },
+ { "1", FR_BOOL_TRUE },
+
+ { "auto", FR_BOOL_AUTO },
+
+ { NULL, 0 }
+};
+
+/*
+ * Get decent values for false / true / auto
+ */
+int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str)
+{
+ int value;
+
+ /*
+ * Don't change anything.
+ */
+ if (!str) return 0;
+
+ value = fr_str2int(fr_bool_auto_names, str, -1);
+ if (value >= 0) {
+ *out = value;
+ return 0;
+ }
+
+ /*
+ * This should never happen, as the defaults are in the
+ * source code. If there's no CONF_PAIR, and there's a
+ * parse error, then the source code is wrong.
+ */
+ if (!cp) {
+ fprintf(stderr, "%s: Error - Invalid value in configuration", main_config.name);
+ return -1;
+ }
+
+ cf_log_err(cf_pair_to_item(cp), "Invalid value for \"%s\"", cf_pair_attr(cp));
+ return -1;
+}
+
+
/**********************************************************************
*
* We need to figure out where the logs go, before doing anything
@@ -159,6 +208,8 @@ static const CONF_PARSER security_config[] = {
{ "max_attributes", FR_CONF_POINTER(PW_TYPE_INTEGER, &fr_max_attributes), STRINGIFY(0) },
{ "reject_delay", FR_CONF_POINTER(PW_TYPE_TIMEVAL, &main_config.reject_delay), STRINGIFY(0) },
{ "status_server", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.status_server), "no"},
+ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING, &require_message_authenticator), "auto"},
+ { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING, &limit_proxy_state), "auto"},
#ifdef ENABLE_OPENSSL_VERSION_CHECK
{ "allow_vulnerable_openssl", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.allow_vulnerable_openssl), "no"},
#endif
@@ -838,6 +889,8 @@ int main_config_init(void)
if (!main_config.dictionary_dir) {
main_config.dictionary_dir = DICTDIR;
}
+ main_config.require_ma = FR_BOOL_AUTO;
+ main_config.limit_proxy_state = FR_BOOL_AUTO;
/*
* About sizeof(REQUEST) + sizeof(RADIUS_PACKET) * 2 + sizeof(VALUE_PAIR) * 400
@@ -1127,6 +1180,23 @@ do {\
main_config.init_delay.tv_sec = 0;
main_config.init_delay.tv_usec = 2* (1000000 / 3);
+ {
+ CONF_PAIR *cp = NULL;
+
+ subcs = cf_section_sub_find(cs, "security");
+ if (subcs) cp = cf_pair_find(subcs, "require_message_authenticator");
+ if (fr_bool_auto_parse(cp, &main_config.require_ma, require_message_authenticator) < 0) {
+ cf_file_free(cs);
+ return -1;
+ }
+
+ if (subcs) cp = cf_pair_find(subcs, "limit_proxy_state");
+ if (fr_bool_auto_parse(cp, &main_config.limit_proxy_state, limit_proxy_state) < 0) {
+ cf_file_free(cs);
+ return -1;
+ }
+ }
+
/*
* Free the old configuration items, and replace them
* with the new ones.
diff --git a/src/main/process.c b/src/main/process.c
index 78c6d8a9e5..a478026024 100644
--- a/src/main/process.c
+++ b/src/main/process.c
@@ -2594,6 +2594,23 @@ int request_proxy_reply(RADIUS_PACKET *packet)
PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+ if (!request->proxy_reply) {
+ decode_fail_t reason;
+
+ /*
+ * If the home server configuration requires a Message-Authenticator, then set the flag,
+ * but only if the proxied packet is Access-Request or Status-Sercer.
+ *
+ * The realms.c file already clears require_ma for TLS connections.
+ */
+ bool require_ma = (request->home_server->require_ma == FR_BOOL_TRUE) && (request->proxy->code == PW_CODE_ACCESS_REQUEST);
+
+ if(!rad_packet_ok(packet, require_ma, &reason)) {
+ DEBUG("Ignoring invalid packet - %s", fr_strerror());
+ return 0;
+ }
+ }
+
/*
* No reply, BUT the current packet fails verification:
* ignore it. This does the MD5 calculations in the
@@ -2619,6 +2636,54 @@ int request_proxy_reply(RADIUS_PACKET *packet)
return 0;
}
+
+ /*
+ * BlastRADIUS checks. We're running in the main
+ * listener thread, so there's no conflict
+ * checking or setting these fields.
+ */
+ if (!request->proxy_reply && (request->proxy->code == PW_CODE_ACCESS_REQUEST) &&
+#ifdef WITH_TLS
+ !request->home_server->tls &&
+#endif
+ !packet->eap_message) {
+ if (request->home_server->require_ma == FR_BOOL_AUTO) {
+ if (!packet->message_authenticator) {
+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ RERROR("BlastRADIUS check: Received response to Access-Request without Message-Authenticator.");
+ RERROR("Setting \"require_message_authenticator = false\" for home_server %s", request->home_server->name);
+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ RERROR("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK.");
+ RERROR("Once the home_server is upgraded, set \"require_message_authenticator = true\" for home_server %s.", request->home_server->name);
+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+
+ request->home_server->require_ma = FR_BOOL_FALSE;
+ } else {
+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ RERROR("BlastRADIUS check: Received response to Access-Request with Message-Authenticator.");
+ RERROR("Setting \"require_message_authenticator = true\" for home_server %s", request->home_server->name);
+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ RERROR("It looks like the home server has been updated to protect from the BlastRADIUS attack.");
+ RERROR("Please set \"require_message_authenticator = true\" for home_server %s", request->home_server->name);
+ RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+
+ request->home_server->require_ma = FR_BOOL_TRUE;
+ }
+
+ } else if (fr_debug_lvl && (request->home_server->require_ma == FR_BOOL_FALSE) && !packet->message_authenticator) {
+ /*
+ * If it's "no" AND we don't have a Message-Authenticator, then complain on every packet.
+ */
+ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ RDEBUG("BlastRADIUS check: Received packet without Message-Authenticator from home_server %s", request->home_server->name);
+ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ RDEBUG("The packet does not contain Message-Authenticator, which is a security issue");
+ RDEBUG("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK.");
+ RERROR("Once the home_server is upgraded, set \"require_message_authenticator = true\" for home_server %s.", request->home_server->name);
+ RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ }
+ }
+
/*
* This shouldn't happen, but threads and race
* conditions.
diff --git a/src/main/radclient.c b/src/main/radclient.c
index 52d2872b13..dd2b342ba2 100644
--- a/src/main/radclient.c
+++ b/src/main/radclient.c
@@ -54,6 +54,7 @@ static fr_ipaddr_t server_ipaddr;
static int resend_count = 1;
static bool done = true;
static bool print_filename = false;
+static bool blast_radius = false;
static fr_ipaddr_t client_ipaddr;
static uint16_t client_port = 0;
@@ -89,6 +90,7 @@ static void NEVER_RETURNS usage(void)
fprintf(stderr, " <command> One of auth, acct, status, coa, disconnect or auto.\n");
fprintf(stderr, " -4 Use IPv4 address of server\n");
fprintf(stderr, " -6 Use IPv6 address of server.\n");
+ fprintf(stderr, " -b Mandate checks for Blast RADIUS (this is not set by default).\n");
fprintf(stderr, " -c <count> Send each packet 'count' times.\n");
fprintf(stderr, " -d <raddb> Set user dictionary directory (defaults to " RADDBDIR ").\n");
fprintf(stderr, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n");
@@ -1000,6 +1002,130 @@ static int send_one_packet(rc_request_t *request)
return 0;
}
+/*
+ * Do Blast RADIUS checks.
+ *
+ * The request is an Access-Request, and does NOT contain Proxy-State.
+ *
+ * The reply is a raw packet, and is NOT yet decoded.
+ */
+static int blast_radius_check(rc_request_t *request, RADIUS_PACKET *reply)
+{
+ uint8_t *attr, *end;
+ VALUE_PAIR *vp;
+ bool have_message_authenticator = false;
+
+ /*
+ * We've received a raw packet. Nothing has (as of yet) checked
+ * anything in it other than the length, and that it's a
+ * well-formed RADIUS packet.
+ */
+ switch (reply->data[0]) {
+ case PW_CODE_ACCESS_ACCEPT:
+ case PW_CODE_ACCESS_REJECT:
+ case PW_CODE_ACCESS_CHALLENGE:
+ if (reply->data[1] != request->packet->id) {
+ ERROR("Invalid reply ID %d to Access-Request ID %d", reply->data[1], request->packet->id);
+ return -1;
+ }
+ break;
+
+ default:
+ ERROR("Invalid reply code %d to Access-Request", reply->data[0]);
+ return -1;
+ }
+
+ /*
+ * If the reply has a Message-Authenticator, then it MIGHT be fine.
+ */
+ attr = reply->data + 20;
+ end = reply->data + reply->data_len;
+
+ /*
+ * It should be the first attribute, so we warn if it isn't there.
+ *
+ * But it's not a fatal error.
+ */
+ if (blast_radius && (attr[0] != PW_MESSAGE_AUTHENTICATOR)) {
+ RDEBUG("WARNING The %s reply packet does not have Message-Authenticator as the first attribute. The packet may be vulnerable to Blast RADIUS attacks.",
+ fr_packet_codes[reply->data[0]]);
+ }
+
+ /*
+ * Set up for Proxy-State checks.
+ *
+ * If we see a Proxy-State in the reply which we didn't send, then it's a Blast RADIUS attack.
+ */
+ vp = fr_pair_find_by_num(request->packet->vps, PW_PROXY_STATE, 0, TAG_ANY);
+
+ while (attr < end) {
+ /*
+ * Blast RADIUS work-arounds require that
+ * Message-Authenticator is the first attribute in the
+ * reply. Note that we don't check for it being the
+ * first attribute, but simply that it exists.
+ *
+ * That check is a balance between securing the reply
+ * packet from attacks, and not violating the RFCs which
+ * say that there is no order to attributes in the
+ * packet.
+ *
+ * However, no matter the status of the '-b' flag we
+ * still can check for the signature of the attack, and
+ * discard packets which are suspicious. This behavior
+ * protects radclient from the attack, without mandating
+ * new behavior on the server side.
+ *
+ * Note that we don't set the '-b' flag by default.
+ * radclient is intended for testing / debugging, and is
+ * not intended to be used as part of a secure login /
+ * user checking system.
+ */
+ if (attr[0] == PW_MESSAGE_AUTHENTICATOR) {
+ have_message_authenticator = true;
+ goto next;
+ }
+
+ /*
+ * If there are Proxy-State attributes in the reply, they must
+ * match EXACTLY the Proxy-State attributes in the request.
+ *
+ * Note that we don't care if there are more Proxy-States
+ * in the request than in the reply. The Blast RADIUS
+ * issue requires _adding_ Proxy-State attributes, and
+ * cannot work when the server _deletes_ Proxy-State
+ * attributes.
+ */
+ if (attr[0] == PW_PROXY_STATE) {
+ if (!vp || (vp->length != (size_t) (attr[1] - 2)) || (memcmp(vp->vp_octets, attr + 2, vp->length) != 0)) {
+ ERROR("Invalid reply to Access-Request ID %d - Discarding packet due to Blast RADIUS attack being detected.", request->packet->id);
+ ERROR("We received a Proxy-State in the reply which we did not send, or which is different from what we sent.");
+ return -1;
+ }
+
+ vp = fr_pair_find_by_num(vp->next, PW_PROXY_STATE, 0, TAG_ANY);
+ }
+
+ next:
+ attr += attr[1];
+ }
+
+ /*
+ * If "-b" is set, then we require Message-Authenticator in the reply.
+ */
+ if (blast_radius && !have_message_authenticator) {
+ ERROR("The %s reply packet does not contain Message-Authenticator - discarding packet due to Blast RADIUS checks.",
+ fr_packet_codes[reply->data[0]]);
+ return -1;
+ }
+
+ /*
+ * The packet doesn't look like it's a Blast RADIUS attack. The
+ * caller will now verify the packet signature.
+ */
+ return 0;
+}
+
/*
* Receive one packet, maybe.
*/
@@ -1051,6 +1177,21 @@ static int recv_one_packet(int wait_time)
}
request = fr_packet2myptr(rc_request_t, packet, packet_p);
+
+ /*
+ * We want radclient to be able to send any packet, including
+ * imperfect ones. However, we do NOT want to be vulnerable to
+ * the "Blast RADIUS" issue. Instead of adding command-line
+ * flags to enable/disable similar flags to what the server
+ * sends, we just do a few more smart checks to double-check
+ * things.
+ */
+ if ((request->packet->code == PW_CODE_ACCESS_REQUEST) &&
+ blast_radius_check(request, reply) < 0) {
+ rad_free(&reply);
+ return -1;
+ }
+
/*
* Fails the signature validation: not a real reply.
* FIXME: Silently drop it and listen for another packet.
@@ -1183,7 +1324,7 @@ int main(int argc, char **argv)
exit(1);
}
- while ((c = getopt(argc, argv, "46c:d:D:f:Fhn:p:qr:sS:t:vx"
+ while ((c = getopt(argc, argv, "46bc:d:D:f:Fhn:p:qr:sS:t:vx"
#ifdef WITH_TCP
"P:"
#endif
@@ -1196,6 +1337,10 @@ int main(int argc, char **argv)
force_af = AF_INET6;
break;
+ case 'b':
+ blast_radius = true;
+ break;
+
case 'c':
if (!isdigit((int) *optarg))
usage();
diff --git a/src/main/radtest.in b/src/main/radtest.in
index 38b1ba9a0f..8a6741a26c 100644
--- a/src/main/radtest.in
+++ b/src/main/radtest.in
@@ -19,6 +19,7 @@ usage() {
echo " -x Enable debug output" >&2
echo " -4 Use IPv4 for the NAS address (default)" >&2
echo " -6 Use IPv6 for the NAS address" >&2
+ echo " -6 Mandate checks for Blast RADIUS (this is not set by default)." >&2
exit 1
}
@@ -55,6 +56,10 @@ do
NAS_ADDR_ATTR="NAS-IPv6-Address"
shift
;;
+ -b)
+ OPTIONS="$OPTIONS -b"
+ shift
+ ;;
-d)
OPTIONS="$OPTIONS -d $2"
shift;shift
@@ -120,7 +125,6 @@ fi
echo "$PASSWORD = \"$2\""
echo "$NAS_ADDR_ATTR = $nas"
echo "NAS-Port = $4"
- echo "Message-Authenticator = 0x00"
if [ "$radclient" = "$radeapclient" ]
then
echo "EAP-Code = Response"
diff --git a/src/main/realms.c b/src/main/realms.c
index eb42598116..c82d2eb1ea 100644
--- a/src/main/realms.c
+++ b/src/main/realms.c
@@ -366,7 +366,10 @@ static CONF_PARSER home_server_coa[] = {
};
#endif
+static const char *require_message_authenticator = NULL;
+
static CONF_PARSER home_server_config[] = {
+ { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL },
{ "ipaddr", FR_CONF_OFFSET(PW_TYPE_COMBO_IP_ADDR, home_server_t, ipaddr), NULL },
{ "ipv4addr", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, home_server_t, ipaddr), NULL },
{ "ipv6addr", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, home_server_t, ipaddr), NULL },
@@ -640,6 +643,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE
home->cs = cs;
home->state = HOME_STATE_UNKNOWN;
home->proto = IPPROTO_UDP;
+ home->require_ma = main_config.require_ma;
+
+ require_message_authenticator = false;
/*
* Parse the configuration into the home server
@@ -647,6 +653,10 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE
*/
if (cf_section_parse(cs, home, home_server_config) < 0) goto error;
+ if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &home->require_ma, require_message_authenticator) < 0) {
+ goto error;
+ }
+
/*
* It has an IP address, it must be a remote server.
*/
@@ -924,6 +934,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE
* Parse the SSL client configuration.
*/
if (tls) {
+ home->require_ma = false;
home->tls = tls_client_conf_parse(tls);
if (!home->tls) {
goto error;
diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c
index 0eed87b64f..4ae3c5b975 100644
--- a/src/main/tls_listen.c
+++ b/src/main/tls_listen.c
@@ -299,6 +299,8 @@ get_application_data:
packet->vps = NULL;
PTHREAD_MUTEX_UNLOCK(&sock->mutex);
+ packet->tls = true;
+
if (!rad_packet_ok(packet, 0, NULL)) {
if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
DEBUG("Closing TLS socket from client");
@@ -713,6 +715,8 @@ int proxy_tls_recv(rad_listen_t *listener)
memcpy(packet->data, data, packet->data_len);
memcpy(packet->vector, packet->data + 4, 16);
+ packet->tls = true;
+
/*
* FIXME: Client MIB updates?
*/
@@ -765,6 +769,7 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request)
* if there's no packet, encode it here.
*/
if (!request->proxy->data) {
+ request->reply->tls = true;
request->proxy_listener->encode(request->proxy_listener,
request);
}