Add MessageAuthenticator support

To address RadiusBLAST vulnerability.

Fixes #16
This commit is contained in:
Samuel Thibault 2024-10-19 22:31:59 +02:00
parent 42ef80e0b4
commit cc012e18fa
8 changed files with 149 additions and 2 deletions

View file

@ -183,6 +183,16 @@ sending of RADIUS interim accounting records (in seconds).</p>
<p>This secret will be used in all RADIUS queries. If this is not set <p>This secret will be used in all RADIUS queries. If this is not set
then RADIUS queries will fail.</p> then RADIUS queries will fail.</p>
</dd> </dd>
<dt><code>radius_require_message_authenticator</code> (string)</dt>
<dd>
<p>If set to true, RADIUS answers to AccessRequests will have to contain
a valid MessageAuthenticator. If set to auto (default), if the first
RADIUS answer to AccessRequests contains a valid MessageAuthenticator,
subsequent answers will have to contain one. If set to no (not
recommended), RADIUS answers to AccessRequests do not have to contain a
valid MessageAuthenticator. It is advised to set this to true after
checking that your RADIUS server does send MessageAuthenticator.</p>
</dd>
<dt><code>radius_authtypes</code> (string)</dt> <dt><code>radius_authtypes</code> (string)</dt>
<dd> <dd>
<p>A comma separated list of supported RADIUS authentication methods <p>A comma separated list of supported RADIUS authentication methods

View file

@ -1,4 +1,4 @@
.\" Automatically generated by Pandoc 3.0.1 .\" Automatically generated by Pandoc 3.1.3
.\" .\"
.\" Define V font for inline verbatim, using C font in formats .\" Define V font for inline verbatim, using C font in formats
.\" that render this, and otherwise B font. .\" that render this, and otherwise B font.
@ -360,6 +360,18 @@ RADIUS interim accounting records (in seconds).
This secret will be used in all RADIUS queries. This secret will be used in all RADIUS queries.
If this is not set then RADIUS queries will fail. If this is not set then RADIUS queries will fail.
.PP .PP
\f[B]radius_require_message_authenticator\f[R] (string)
.PP
If set to true, RADIUS answers to AccessRequests will have to contain a
valid MessageAuthenticator.
If set to auto (default), if the first RADIUS answer to AccessRequests
contains a valid MessageAuthenticator, subsequent answers will have to
contain one.
If set to no (not recommended), RADIUS answers to AccessRequests do not
have to contain a valid MessageAuthenticator.
It is advised to set this to true after checking that your RADIUS server
does send MessageAuthenticator.
.PP
\f[B]radius_authtypes\f[R] (string) \f[B]radius_authtypes\f[R] (string)
.PP .PP
A comma separated list of supported RADIUS authentication methods A comma separated list of supported RADIUS authentication methods

View file

@ -203,6 +203,18 @@ should be set by a line like: set configstring \"value\" set ipaddress
: This secret will be used in all RADIUS queries. If this is not set : This secret will be used in all RADIUS queries. If this is not set
then RADIUS queries will fail. then RADIUS queries will fail.
`radius_require_message_authenticator` (string)
: If set to true, RADIUS answers to AccessRequests will have to contain
a valid MessageAuthenticator.
If set to auto (default), if the first RADIUS answer to AccessRequests
contains a valid MessageAuthenticator, subsequent answers will have to
contain one.
If set to no (not recommended), RADIUS answers to AccessRequests do not have
to contain a valid MessageAuthenticator.
It is advised to set this to true after checking that your RADIUS server
does send MessageAuthenticator.
`radius_authtypes` (string) `radius_authtypes` (string)
: A comma separated list of supported RADIUS authentication methods : A comma separated list of supported RADIUS authentication methods

View file

@ -215,6 +215,18 @@ The following `variables` may be set:
This secret will be used in all RADIUS queries. If this is not set then RADIUS queries will fail. This secret will be used in all RADIUS queries. If this is not set then RADIUS queries will fail.
**radius\_require\_message\_authenticator** (string)
If set to true, RADIUS answers to AccessRequests will have to contain
a valid MessageAuthenticator.
If set to auto (default), if the first RADIUS answer to AccessRequests
contains a valid MessageAuthenticator, subsequent answers will have to
contain one.
If set to no (not recommended), RADIUS answers to AccessRequests do not have
to contain a valid MessageAuthenticator.
It is advised to set this to true after checking that your RADIUS server
does send MessageAuthenticator.
**radius\_authtypes** (string) **radius\_authtypes** (string)
A comma separated list of supported RADIUS authentication methods ("pap" or "chap"), in order of preference (default "pap"). A comma separated list of supported RADIUS authentication methods ("pap" or "chap"), in order of preference (default "pap").

View file

@ -34,6 +34,8 @@ set primary_radius 10.0.0.3
#set secondary_radius 0.0.0.0 #set secondary_radius 0.0.0.0
#set secondary_radius_port 1812 #set secondary_radius_port 1812
set radius_secret "secret" set radius_secret "secret"
# Set this to yes once you have confirmed that your RADIUS server provides MessageAuthenticator in its responses
#set radius_require_message_authenticator auto
# Acceptable authentication types (pap, chap) in order of preference # Acceptable authentication types (pap, chap) in order of preference
#set radius_authtypes "pap" #set radius_authtypes "pap"

View file

@ -166,6 +166,7 @@ config_descriptt config_values[] = {
CONFIG("radius_accounting", radius_accounting, BOOL), CONFIG("radius_accounting", radius_accounting, BOOL),
CONFIG("radius_interim", radius_interim, INT), CONFIG("radius_interim", radius_interim, INT),
CONFIG("radius_secret", radiussecret, STRING), CONFIG("radius_secret", radiussecret, STRING),
CONFIG("radius_require_message_authenticator", radius_require_message_authenticator, STRING),
CONFIG("radius_authtypes", radius_authtypes_s, STRING), CONFIG("radius_authtypes", radius_authtypes_s, STRING),
CONFIG("radius_dae_port", radius_dae_port, SHORT), CONFIG("radius_dae_port", radius_dae_port, SHORT),
CONFIG("radius_bind_min", radius_bind_min, SHORT), CONFIG("radius_bind_min", radius_bind_min, SHORT),

View file

@ -732,6 +732,7 @@ typedef struct
int ppp_keepalive; // send echoes regardless int ppp_keepalive; // send echoes regardless
char radiussecret[64]; char radiussecret[64];
char radius_require_message_authenticator[5];
int radius_accounting; int radius_accounting;
int radius_interim; int radius_interim;
in_addr_t radiusserver[MAXRADSERVER]; // radius servers in_addr_t radiusserver[MAXRADSERVER]; // radius servers

View file

@ -32,6 +32,10 @@ extern ip_filtert *ip_filters;
static const hasht zero; static const hasht zero;
static enum {
MAYBE = 0, YES, NO,
} radius_sends_message_authenticator[MAXRADSERVER];
static void calc_auth(const void *buf, size_t len, const uint8_t *in, uint8_t *out) static void calc_auth(const void *buf, size_t len, const uint8_t *in, uint8_t *out)
{ {
MD5_CTX ctx; MD5_CTX ctx;
@ -163,7 +167,7 @@ void radiussend(uint16_t r, uint8_t state)
uint8_t b[4096]; // RADIUS packet uint8_t b[4096]; // RADIUS packet
char pass[129]; char pass[129];
int pl; int pl;
uint8_t *p; uint8_t *p, *ma = NULL;
sessionidt s; sessionidt s;
CSTAT(radiussend); CSTAT(radiussend);
@ -237,6 +241,16 @@ void radiussend(uint16_t r, uint8_t state)
memcpy(b + 4, radius[r].auth, 16); memcpy(b + 4, radius[r].auth, 16);
p = b + 20; p = b + 20;
if (state == RADIUSAUTH || state == RADIUSJUSTAUTH)
{
*p = 80; // MessageAuthenticator
p[1] = 18; // length
ma = p+2;
memset(ma, 0, 16); // Zero for the computation
p += p[1];
}
if (s) if (s)
{ {
*p = 1; // user name *p = 1; // user name
@ -464,12 +478,18 @@ void radiussend(uint16_t r, uint8_t state)
// All AVpairs added // All AVpairs added
*(uint16_t *) (b + 2) = htons(p - b); *(uint16_t *) (b + 2) = htons(p - b);
if (state != RADIUSAUTH && state != RADIUSJUSTAUTH) if (state != RADIUSAUTH && state != RADIUSJUSTAUTH)
{ {
// Build auth for accounting packet // Build auth for accounting packet
calc_auth(b, p - b, zero, b + 4); calc_auth(b, p - b, zero, b + 4);
memcpy(radius[r].auth, b + 4, 16); memcpy(radius[r].auth, b + 4, 16);
} }
// Compute MessageAuthenticator
if (ma)
MD5_Hmac(ma, b, p-b, config->radiussecret, strlen(config->radiussecret));
memset(&addr, 0, sizeof(addr)); memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
*(uint32_t *) & addr.sin_addr = config->radiusserver[(radius[r].try - 1) % config->numradiusservers]; *(uint32_t *) & addr.sin_addr = config->radiusserver[(radius[r].try - 1) % config->numradiusservers];
@ -587,6 +607,83 @@ void processrad(uint8_t *buf, int len, char socket_index)
if (radius[r].state == RADIUSAUTH || radius[r].state == RADIUSJUSTAUTH) if (radius[r].state == RADIUSAUTH || radius[r].state == RADIUSJUSTAUTH)
{ {
// First check MessageAuthenticator
{
uint8_t *p = buf + 20;
uint8_t *e = buf + len;
int seen = 0, want;
for (; p + 2 <= e && p[1] && p + p[1] <= e; p += p[1])
{
if (*p == 80)
{
// MessageAuthenticator
uint8_t auth[16];
uint8_t authcheck[16];
uint8_t vector[16];
/* Set original vector for computation */
memcpy(vector, buf+4, 16);
memcpy(buf+4, radius[r].auth, 16);
/* Set authenticator to zero for computation */
memcpy(auth, p+2, 16);
memset(p+2, 0, 16);
MD5_Hmac(authcheck, buf, len, config->radiussecret, strlen(config->radiussecret));
if (memcmp(auth, authcheck, 16) != 0)
{
LOG(1, 0, 0, "Incorrect MessageAuthenticator in Access reply (wrong secret in radius config?)\n");
return;
}
/* Restore vector */
memcpy(buf+4, vector, 16);
/* Restore authenticator */
memcpy(p+2, auth, 16);
seen = 1;
}
}
if (!strcmp(config->radius_require_message_authenticator, "no"))
want = 0;
else if (!strcmp(config->radius_require_message_authenticator, "yes"))
want = 1;
else
{
if (config->radius_require_message_authenticator[0]
&& strcmp(config->radius_require_message_authenticator, "auto"))
LOG(1, 0, 0, "Unrecognized radius_require_message_authenticator '%s', assuming auto\n",
config->radius_require_message_authenticator);
int num = radius[r].try % config->numradiusservers;
switch (radius_sends_message_authenticator[num]) {
case YES:
want = 1;
break;
case NO:
want = 0;
break;
case MAYBE:
want = seen;
if (seen)
radius_sends_message_authenticator[num] = YES;
else
radius_sends_message_authenticator[num] = NO;
break;
}
}
if (want && !seen)
{
LOG(1, 0, 0, "Missing MessageAuthenticator in Access reply\n");
return;
}
}
// run post-auth plugin // run post-auth plugin
struct param_post_auth packet = { struct param_post_auth packet = {
&tunnel[t], &tunnel[t],