From: Alan T. DeKok Date: Wed, 10 Apr 2024 21:34:31 +0000 (-0400) Subject: add Blast RADIUS checks to radclient and radtest X-Git-Tag: release_3_0_27~11 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=43587a6b378204ef82c8b3578c31d4ef70c00083;p=thirdparty%2Ffreeradius-server.git add Blast RADIUS checks to radclient and radtest --- 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 2bf899797a..d90651aba8 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/src/main/radclient.c b/src/main/radclient.c index caed2f1e53..b48d97235e 100644 --- a/src/main/radclient.c +++ b/src/main/radclient.c @@ -60,6 +60,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; @@ -95,6 +96,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"); @@ -1056,6 +1058,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. */ @@ -1107,6 +1233,20 @@ 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. @@ -1240,7 +1380,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 @@ -1253,6 +1393,10 @@ int main(int argc, char **argv) force_af = AF_INET6; break; + case 'b': + blast_radius = true; + break; + case 'c': if (!isdigit((uint8_t) *optarg)) usage(); diff --git a/src/main/radtest.in b/src/main/radtest.in index 6b7103214f..af9df80eec 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"