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:
@ -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) */
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user