diff options
Diffstat (limited to 'src/main/radclient.c')
-rw-r--r-- | src/main/radclient.c | 205 |
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; } |