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;
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");
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.
*/
}
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.
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
force_af = AF_INET6;
break;
+ case 'b':
+ blast_radius = true;
+ break;
+
case 'c':
if (!isdigit((uint8_t) *optarg)) usage();