diff --git a/freeradius-blastradius-fix.patch b/freeradius-blastradius-fix.patch new file mode 100644 index 0000000..9b64838 --- /dev/null +++ b/freeradius-blastradius-fix.patch @@ -0,0 +1,1502 @@ +From: Antonio Torres +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 +--- + 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 + + #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, " 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 Send each packet 'count' times.\n"); + fprintf(stderr, " -d Set user dictionary directory (defaults to " RADDBDIR ").\n"); + fprintf(stderr, " -D 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); + } diff --git a/freeradius.spec b/freeradius.spec index a7b02fe..23e58b0 100644 --- a/freeradius.spec +++ b/freeradius.spec @@ -9,7 +9,7 @@ Summary: High-performance and highly configurable free RADIUS server Name: freeradius Version: 3.0.20 -Release: 14%{?dist} +Release: 15%{?dist} License: GPLv2+ and LGPLv2+ Group: System Environment/Daemons URL: http://www.freeradius.org/ @@ -47,6 +47,7 @@ Patch14: freeradius-Fix-segfault-when-home_server-is-null.patch Patch15: freeradius-fix-crash-on-invalid-abinary-data.patch Patch16: freeradius-fix-crash-unknown-eap-sim.patch Patch17: freeradius-fix-info-leakage-eap-pwd.patch +Patch18: freeradius-blastradius-fix.patch %global docdir %{?_pkgdocdir}%{!?_pkgdocdir:%{_docdir}/%{name}-%{version}} @@ -252,6 +253,7 @@ This plugin provides the REST support for the FreeRADIUS server project. %patch15 -p1 %patch16 -p1 %patch17 -p1 +%patch18 -p1 # Add fixed dhparam file to the source to ensure `make tests` can run. cp %{SOURCE105} raddb/certs/rfc3526-group-18-8192.dhparam @@ -902,6 +904,10 @@ exit 0 %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/rest %changelog +* Thu Jul 11 2023 Antonio Torres - 3.0.20-15 +- Backport BlastRADIUS CVE fix + Resolves: RHEL-46572 + * Fri Dec 14 2022 Antonio Torres - 3.0.20-14 - Fix defect found by Covscan Resolves: #2151704