summaryrefslogtreecommitdiffstats
path: root/src/main/radclient.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/radclient.c')
-rw-r--r--src/main/radclient.c205
1 files changed, 200 insertions, 5 deletions
diff --git a/src/main/radclient.c b/src/main/radclient.c
index 49da461..ab880dd 100644
--- a/src/main/radclient.c
+++ b/src/main/radclient.c
@@ -60,11 +60,13 @@ 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;
static int sockfd;
+static int last_used_id = -1;
#ifdef WITH_TCP
static char const *proto = NULL;
@@ -95,6 +97,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 issue (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");
@@ -416,7 +419,7 @@ static int radclient_init(TALLOC_CTX *ctx, rc_file_pair_t *files)
#endif
request->files = files;
- request->packet->id = -1; /* allocate when sending */
+ request->packet->id = last_used_id; /* either requested, or allocated by the library */
request->num = num++;
/*
@@ -892,7 +895,7 @@ static int send_one_packet(rc_request_t *request)
/*
* Haven't sent the packet yet. Initialize it.
*/
- if (request->packet->id == -1) {
+ if (!request->tries || (request->packet->id == -1)) {
int i;
bool rcode;
@@ -949,8 +952,18 @@ static int send_one_packet(rc_request_t *request)
assert(request->packet->id != -1);
assert(request->packet->data == NULL);
- for (i = 0; i < 4; i++) {
- ((uint32_t *) request->packet->vector)[i] = fr_rand();
+ if (request->packet->code == PW_CODE_ACCESS_REQUEST) {
+ VALUE_PAIR *vp;
+
+ if (((vp = fr_pair_find_by_num(request->packet->vps, PW_PACKET_AUTHENTICATION_VECTOR, 0, TAG_ANY)) != NULL) &&
+ (vp->vp_length >= 16)) {
+ memcpy(request->packet->vector, vp->vp_octets, 16);
+
+ } else {
+ for (i = 0; i < 4; i++) {
+ ((uint32_t *) request->packet->vector)[i] = fr_rand();
+ }
+ }
}
/*
@@ -1048,11 +1061,15 @@ static int send_one_packet(rc_request_t *request)
REDEBUG("Failed to send packet for ID %d", request->packet->id);
deallocate_id(request);
request->done = true;
+ stats.lost++;
return -1;
}
if (fr_log_fp) {
fr_packet_header_print(fr_log_fp, request->packet, false);
+
+ if (fr_debug_lvl > 2) rad_print_hex(request->packet);
+
if (fr_debug_lvl > 0) vp_printlist(fr_log_fp, request->packet->vps);
}
@@ -1060,6 +1077,130 @@ static int send_one_packet(rc_request_t *request)
}
/*
+ * 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.
*/
static int recv_one_packet(int wait_time)
@@ -1101,6 +1242,8 @@ static int recv_one_packet(int wait_time)
return -1; /* bad packet */
}
+ if (fr_debug_lvl > 2) rad_print_hex(reply);
+
packet_p = fr_packet_list_find_byreply(pl, reply);
if (!packet_p) {
ERROR("Received reply to request we did not send. (id=%d socket %d)",
@@ -1111,6 +1254,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.
*/
@@ -1243,7 +1400,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:Fhi:n:p:qr:sS:t:vx"
#ifdef WITH_TCP
"P:"
#endif
@@ -1256,6 +1413,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();
@@ -1297,6 +1458,15 @@ int main(int argc, char **argv)
print_filename = true;
break;
+ case 'i':
+ if (!isdigit((uint8_t) *optarg))
+ usage();
+ last_used_id = atoi(optarg);
+ if ((last_used_id < 0) || (last_used_id > 255)) {
+ usage();
+ }
+ break;
+
case 'n':
persec = atoi(optarg);
if (persec <= 0) usage();
@@ -1562,6 +1732,7 @@ int main(int argc, char **argv)
int n = parallel;
rc_request_t *next;
char const *filename = NULL;
+ time_t wake = 0;
done = true;
sleep_time = -1;
@@ -1569,6 +1740,15 @@ int main(int argc, char **argv)
/*
* Walk over the packets, sending them.
*/
+ for (this = request_head; this != NULL; this = this->next) {
+ if (this->reply) continue;
+
+ if (!this->timestamp) continue;
+
+ if (!wake || (wake > (this->timestamp + ((int) timeout) * (retries - this->tries)))) {
+ wake = this->timestamp + ((int) timeout) * (retries - this->tries);
+ }
+ }
for (this = request_head; this != NULL; this = next) {
next = this->next;
@@ -1613,6 +1793,10 @@ int main(int argc, char **argv)
break;
}
+ if (!wake || (wake > (this->timestamp + ((int) timeout) * (retries - this->tries)))) {
+ wake = this->timestamp + ((int) timeout) * (retries - this->tries);
+ }
+
/*
* Wait a little before sending
* the next packet, if told to.
@@ -1664,7 +1848,18 @@ int main(int argc, char **argv)
* Still have outstanding requests.
*/
if (fr_packet_list_num_elements(pl) > 0) {
+ time_t now = time(NULL);
done = false;
+
+ /*
+ * The last time we wake up for a packet.
+ *
+ * If we're past that time, then give up.
+ */
+ if (wake < now) {
+ break;
+ }
+
} else {
sleep_time = 0;
}