1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-12 21:01:52 +03:00

Fix low-risk potential denial of service against RADIUS login.

Corrupt RADIUS responses were treated as errors and not ignored
(which the RFC2865 states they should be). This meant that a
user with unfiltered access to the network of the PostgreSQL
or RADIUS server could send a spoofed RADIUS response
to the PostgreSQL server causing it to reject a valid login,
provided the attacker could also guess (or brute-force) the
correct port number.

Fix is to simply retry the receive in a loop until the timeout
has expired or a valid (signed by the correct RADIUS server)
packet arrives.

Reported by Alan DeKok in bug #5687.
This commit is contained in:
Magnus Hagander
2010-10-15 16:59:10 +02:00
parent 915116bc62
commit 0e7f7071e8

View File

@ -2619,7 +2619,7 @@ CheckRADIUSAuth(Port *port)
char portstr[128]; char portstr[128];
ACCEPT_TYPE_ARG3 addrsize; ACCEPT_TYPE_ARG3 addrsize;
fd_set fdset; fd_set fdset;
struct timeval timeout; struct timeval endtime;
int i, int i,
r; r;
@ -2777,14 +2777,36 @@ CheckRADIUSAuth(Port *port)
/* Don't need the server address anymore */ /* Don't need the server address anymore */
pg_freeaddrinfo_all(hint.ai_family, serveraddrs); pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
/* Wait for a response */ /*
timeout.tv_sec = RADIUS_TIMEOUT; * Figure out at what time we should time out. We can't just use
timeout.tv_usec = 0; * a single call to select() with a timeout, since somebody can
FD_ZERO(&fdset); * be sending invalid packets to our port thus causing us to
FD_SET(sock, &fdset); * retry in a loop and never time out.
*/
gettimeofday(&endtime, NULL);
endtime.tv_sec += RADIUS_TIMEOUT;
while (true) while (true)
{ {
struct timeval timeout;
struct timeval now;
int64 timeoutval;
gettimeofday(&now, NULL);
timeoutval = (endtime.tv_sec * 1000000 + endtime.tv_usec) - (now.tv_sec * 1000000 + now.tv_usec);
if (timeoutval <= 0)
{
ereport(LOG,
(errmsg("timeout waiting for RADIUS response")));
closesocket(sock);
return STATUS_ERROR;
}
timeout.tv_sec = timeoutval / 1000000;
timeout.tv_usec = timeoutval % 1000000;
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
r = select(sock + 1, &fdset, NULL, NULL, &timeout); r = select(sock + 1, &fdset, NULL, NULL, &timeout);
if (r < 0) if (r < 0)
{ {
@ -2805,107 +2827,117 @@ CheckRADIUSAuth(Port *port)
return STATUS_ERROR; return STATUS_ERROR;
} }
/* else we actually have a packet ready to read */ /*
break; * Attempt to read the response packet, and verify the contents.
} *
* Any packet that's not actually a RADIUS packet, or otherwise
* does not validate as an explicit reject, is just ignored and
* we retry for another packet (until we reach the timeout). This
* is to avoid the possibility to denial-of-service the login by
* flooding the server with invalid packets on the port that
* we're expecting the RADIUS response on.
*/
/* Read the response packet */ addrsize = sizeof(remoteaddr);
addrsize = sizeof(remoteaddr); packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0,
packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0, (struct sockaddr *) & remoteaddr, &addrsize);
(struct sockaddr *) & remoteaddr, &addrsize); if (packetlength < 0)
if (packetlength < 0) {
{ ereport(LOG,
ereport(LOG, (errmsg("could not read RADIUS response: %m")));
(errmsg("could not read RADIUS response: %m"))); return STATUS_ERROR;
closesocket(sock); }
return STATUS_ERROR;
}
closesocket(sock);
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
if (remoteaddr.sin6_port != htons(port->hba->radiusport)) if (remoteaddr.sin6_port != htons(port->hba->radiusport))
#else #else
if (remoteaddr.sin_port != htons(port->hba->radiusport)) if (remoteaddr.sin_port != htons(port->hba->radiusport))
#endif #endif
{ {
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
ereport(LOG, ereport(LOG,
(errmsg("RADIUS response was sent from incorrect port: %i", (errmsg("RADIUS response was sent from incorrect port: %i",
ntohs(remoteaddr.sin6_port)))); ntohs(remoteaddr.sin6_port))));
#else #else
ereport(LOG, ereport(LOG,
(errmsg("RADIUS response was sent from incorrect port: %i", (errmsg("RADIUS response was sent from incorrect port: %i",
ntohs(remoteaddr.sin_port)))); ntohs(remoteaddr.sin_port))));
#endif #endif
return STATUS_ERROR; continue;
} }
if (packetlength < RADIUS_HEADER_LENGTH) if (packetlength < RADIUS_HEADER_LENGTH)
{ {
ereport(LOG, ereport(LOG,
(errmsg("RADIUS response too short: %i", packetlength))); (errmsg("RADIUS response too short: %i", packetlength)));
return STATUS_ERROR; continue;
} }
if (packetlength != ntohs(receivepacket->length)) if (packetlength != ntohs(receivepacket->length))
{ {
ereport(LOG, ereport(LOG,
(errmsg("RADIUS response has corrupt length: %i (actual length %i)", (errmsg("RADIUS response has corrupt length: %i (actual length %i)",
ntohs(receivepacket->length), packetlength))); ntohs(receivepacket->length), packetlength)));
return STATUS_ERROR; continue;
} }
if (packet->id != receivepacket->id) if (packet->id != receivepacket->id)
{ {
ereport(LOG, ereport(LOG,
(errmsg("RADIUS response is to a different request: %i (should be %i)", (errmsg("RADIUS response is to a different request: %i (should be %i)",
receivepacket->id, packet->id))); receivepacket->id, packet->id)));
return STATUS_ERROR; continue;
} }
/* /*
* Verify the response authenticator, which is calculated as * Verify the response authenticator, which is calculated as
* MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret) * MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
*/ */
cryptvector = palloc(packetlength + strlen(port->hba->radiussecret)); cryptvector = palloc(packetlength + strlen(port->hba->radiussecret));
memcpy(cryptvector, receivepacket, 4); /* code+id+length */ memcpy(cryptvector, receivepacket, 4); /* code+id+length */
memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH); /* request memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH); /* request
* authenticator, from * authenticator, from
* original packet */ * original packet */
if (packetlength > RADIUS_HEADER_LENGTH) /* there may be no attributes if (packetlength > RADIUS_HEADER_LENGTH) /* there may be no attributes
* at all */ * at all */
memcpy(cryptvector + RADIUS_HEADER_LENGTH, receive_buffer + RADIUS_HEADER_LENGTH, packetlength - RADIUS_HEADER_LENGTH); memcpy(cryptvector + RADIUS_HEADER_LENGTH, receive_buffer + RADIUS_HEADER_LENGTH, packetlength - RADIUS_HEADER_LENGTH);
memcpy(cryptvector + packetlength, port->hba->radiussecret, strlen(port->hba->radiussecret)); memcpy(cryptvector + packetlength, port->hba->radiussecret, strlen(port->hba->radiussecret));
if (!pg_md5_binary(cryptvector, if (!pg_md5_binary(cryptvector,
packetlength + strlen(port->hba->radiussecret), packetlength + strlen(port->hba->radiussecret),
encryptedpassword)) encryptedpassword))
{ {
ereport(LOG, ereport(LOG,
(errmsg("could not perform MD5 encryption of received packet"))); (errmsg("could not perform MD5 encryption of received packet")));
pfree(cryptvector);
continue;
}
pfree(cryptvector); pfree(cryptvector);
return STATUS_ERROR;
}
pfree(cryptvector);
if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0) if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
{ {
ereport(LOG, ereport(LOG,
(errmsg("RADIUS response has incorrect MD5 signature"))); (errmsg("RADIUS response has incorrect MD5 signature")));
return STATUS_ERROR; continue;
} }
if (receivepacket->code == RADIUS_ACCESS_ACCEPT) if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
return STATUS_OK; {
else if (receivepacket->code == RADIUS_ACCESS_REJECT) closesocket(sock);
return STATUS_ERROR; return STATUS_OK;
else }
{ else if (receivepacket->code == RADIUS_ACCESS_REJECT)
ereport(LOG, {
(errmsg("RADIUS response has invalid code (%i) for user \"%s\"", closesocket(sock);
receivepacket->code, port->user_name))); return STATUS_ERROR;
return STATUS_ERROR; }
} else
{
ereport(LOG,
(errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
receivepacket->code, port->user_name)));
continue;
}
} /* while (true) */
} }