]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add Blast RADIUS checks to radclient and radtest
authorAlan T. DeKok <aland@freeradius.org>
Wed, 10 Apr 2024 21:34:31 +0000 (17:34 -0400)
committerMatthew Newton <matthew-git@newtoncomputing.co.uk>
Mon, 8 Jul 2024 19:40:57 +0000 (20:40 +0100)
man/man1/radclient.1
man/man1/radtest.1
src/main/radclient.c
src/main/radtest.in

index 229dcae0c7ed0444593cefb8ffa86a2ee36df157..b83bee931a7472b38204271d5d58d33afcac9d91 100644 (file)
@@ -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
index 2bf899797af1fc1baf11eadf929755f1bacb4932..d90651aba86219f3ae47de95434fbc9ce603728d 100644 (file)
@@ -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.
index caed2f1e5385272bc5c9ae438fb754336b01b1b5..b48d97235e37896e2e8a81011756a8ba7fdda09a 100644 (file)
@@ -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, "  <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");
@@ -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();
 
index 6b7103214f283fd0fd8ac61eda852a92c2008d33..af9df80eeccff4e80183f73242c6e3ca8a0685c9 100644 (file)
@@ -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"