On Tue, Oct 5, 2010 at 11:01, Magnus Hagander <[email protected]> wrote:
> On Sun, Oct 3, 2010 at 18:30, Alan T DeKok <[email protected]> wrote:
>> Tom Lane wrote:
>>> Hm ... seems to me that is a network security problem, not our problem.
>>> Who's to say one of the spoofed packets won't pass verification?
>>
>> The packets are signed with a shared key. Passing verification means
>> either the attacker knows the key, or the attacker has broken MD5 in
>> ways that are currently unknown.
>>
>>> If you want to change it, I won't stand in the way, but I have real
>>> doubts about both the credibility of this threat and the usefulness
>>> of the proposed fix.
>>
>> The credibility of the threat is high. Anyone can trivially send a
>> packet which will cause authentication to fail. This is a DoS attack.
>
> I don't agree about how high it is - unless I misunderstand the
> wording. You still need to have unfiltered access to the network that
> the database server is on (unlikely) and you need to guess/bruteforce
> the port (using bruteforce not really hard, but likely to be detected
> by an IDS pretty quickly)
>
> It is definitely an opportunity for a DoS attack though, so it should be
> fixed.
>
> I find your suggested patches kind of hard to read posted inline that
> way - any chance you can repost as attachment or publish it as a git
> repository I can fetch from?
Actually, nevermind that one. Here's a patch I worked up from your
description, and that turns out to be fairly similar to yours in what
it does I think - except I'm not rearranging the code into a separate
function. We already have a while-loop.
See attached context diff, and I've also included a diff without
whitespace changes since the majority of the diff is otherwise coming
from indenting the code one tab...
(so far untested, I seem to have deleted my test-instance of the
radius server, but I figured I should post my attempt anyway)
Also, my patch does not change from log to warning - note that warning
is actually *below* log when it comes to the logfile (see
log_min_messages comments in postgresql.conf). I keep making that
mistake myself...
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 2619,2625 **** CheckRADIUSAuth(Port *port)
char portstr[128];
ACCEPT_TYPE_ARG3 addrsize;
fd_set fdset;
! struct timeval timeout;
int i,
r;
--- 2619,2625 ----
char portstr[128];
ACCEPT_TYPE_ARG3 addrsize;
fd_set fdset;
! struct timeval endtime;
int i,
r;
***************
*** 2777,2790 **** CheckRADIUSAuth(Port *port)
/* Don't need the server address anymore */
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
! /* Wait for a response */
! timeout.tv_sec = RADIUS_TIMEOUT;
! timeout.tv_usec = 0;
! FD_ZERO(&fdset);
! FD_SET(sock, &fdset);
while (true)
{
r = select(sock + 1, &fdset, NULL, NULL, &timeout);
if (r < 0)
{
--- 2777,2812 ----
/* Don't need the server address anymore */
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
! /*
! * Figure out at what time we should time out. We can't just use
! * a single call to select() with a timeout, since somebody can
! * be sending invalid packets to our port thus causing us to
! * retry in a loop and never time out.
! */
! gettimeofday(&endtime, NULL);
! endtime.tv_sec += RADIUS_TIMEOUT;
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);
if (r < 0)
{
***************
*** 2805,2911 **** CheckRADIUSAuth(Port *port)
return STATUS_ERROR;
}
! /* else we actually have a packet ready to read */
! break;
! }
!
! /* Read the response packet */
! addrsize = sizeof(remoteaddr);
! packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0,
! (struct sockaddr *) & remoteaddr, &addrsize);
! if (packetlength < 0)
! {
! ereport(LOG,
! (errmsg("could not read RADIUS response: %m")));
! closesocket(sock);
! return STATUS_ERROR;
! }
! closesocket(sock);
#ifdef HAVE_IPV6
! if (remoteaddr.sin6_port != htons(port->hba->radiusport))
#else
! if (remoteaddr.sin_port != htons(port->hba->radiusport))
#endif
! {
#ifdef HAVE_IPV6
! ereport(LOG,
! (errmsg("RADIUS response was sent from incorrect port: %i",
! ntohs(remoteaddr.sin6_port))));
#else
! ereport(LOG,
! (errmsg("RADIUS response was sent from incorrect port: %i",
! ntohs(remoteaddr.sin_port))));
#endif
! return STATUS_ERROR;
! }
!
! if (packetlength < RADIUS_HEADER_LENGTH)
! {
! ereport(LOG,
! (errmsg("RADIUS response too short: %i", packetlength)));
! return STATUS_ERROR;
! }
!
! if (packetlength != ntohs(receivepacket->length))
! {
! ereport(LOG,
! (errmsg("RADIUS response has corrupt length: %i (actual length %i)",
! ntohs(receivepacket->length), packetlength)));
! return STATUS_ERROR;
! }
! if (packet->id != receivepacket->id)
! {
! ereport(LOG,
! (errmsg("RADIUS response is to a different request: %i (should be %i)",
! receivepacket->id, packet->id)));
! return STATUS_ERROR;
! }
! /*
! * Verify the response authenticator, which is calculated as
! * MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
! */
! cryptvector = palloc(packetlength + strlen(port->hba->radiussecret));
! memcpy(cryptvector, receivepacket, 4); /* code+id+length */
! memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH); /* request
! * authenticator, from
! * original packet */
! if (packetlength > RADIUS_HEADER_LENGTH) /* there may be no attributes
! * at all */
! memcpy(cryptvector + RADIUS_HEADER_LENGTH, receive_buffer + RADIUS_HEADER_LENGTH, packetlength - RADIUS_HEADER_LENGTH);
! memcpy(cryptvector + packetlength, port->hba->radiussecret, strlen(port->hba->radiussecret));
! if (!pg_md5_binary(cryptvector,
! packetlength + strlen(port->hba->radiussecret),
! encryptedpassword))
! {
! ereport(LOG,
! (errmsg("could not perform MD5 encryption of received packet")));
pfree(cryptvector);
- return STATUS_ERROR;
- }
- pfree(cryptvector);
! if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
! {
! ereport(LOG,
! (errmsg("RADIUS response has incorrect MD5 signature")));
! return STATUS_ERROR;
! }
! if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
! return STATUS_OK;
! else if (receivepacket->code == RADIUS_ACCESS_REJECT)
! return STATUS_ERROR;
! else
! {
! ereport(LOG,
! (errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
! receivepacket->code, port->user_name)));
! return STATUS_ERROR;
! }
}
--- 2827,2943 ----
return STATUS_ERROR;
}
! /*
! * 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.
! */
! addrsize = sizeof(remoteaddr);
! packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0,
! (struct sockaddr *) & remoteaddr, &addrsize);
! if (packetlength < 0)
! {
! ereport(LOG,
! (errmsg("could not read RADIUS response: %m")));
! return STATUS_ERROR;
! }
#ifdef HAVE_IPV6
! if (remoteaddr.sin6_port != htons(port->hba->radiusport))
#else
! if (remoteaddr.sin_port != htons(port->hba->radiusport))
#endif
! {
#ifdef HAVE_IPV6
! ereport(LOG,
! (errmsg("RADIUS response was sent from incorrect port: %i",
! ntohs(remoteaddr.sin6_port))));
#else
! ereport(LOG,
! (errmsg("RADIUS response was sent from incorrect port: %i",
! ntohs(remoteaddr.sin_port))));
#endif
! continue;
! }
! if (packetlength < RADIUS_HEADER_LENGTH)
! {
! ereport(LOG,
! (errmsg("RADIUS response too short: %i", packetlength)));
! continue;
! }
! if (packetlength != ntohs(receivepacket->length))
! {
! ereport(LOG,
! (errmsg("RADIUS response has corrupt length: %i (actual length %i)",
! ntohs(receivepacket->length), packetlength)));
! continue;
! }
! if (packet->id != receivepacket->id)
! {
! ereport(LOG,
! (errmsg("RADIUS response is to a different request: %i (should be %i)",
! receivepacket->id, packet->id)));
! continue;
! }
! /*
! * Verify the response authenticator, which is calculated as
! * MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
! */
! cryptvector = palloc(packetlength + strlen(port->hba->radiussecret));
!
! memcpy(cryptvector, receivepacket, 4); /* code+id+length */
! memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH); /* request
! * authenticator, from
! * original packet */
! if (packetlength > RADIUS_HEADER_LENGTH) /* there may be no attributes
! * at all */
! memcpy(cryptvector + RADIUS_HEADER_LENGTH, receive_buffer + RADIUS_HEADER_LENGTH, packetlength - RADIUS_HEADER_LENGTH);
! memcpy(cryptvector + packetlength, port->hba->radiussecret, strlen(port->hba->radiussecret));
!
! if (!pg_md5_binary(cryptvector,
! packetlength + strlen(port->hba->radiussecret),
! encryptedpassword))
! {
! ereport(LOG,
! (errmsg("could not perform MD5 encryption of received packet")));
! pfree(cryptvector);
! continue;
! }
pfree(cryptvector);
! if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
! {
! ereport(LOG,
! (errmsg("RADIUS response has incorrect MD5 signature")));
! continue;
! }
! if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
! {
! closesocket(sock);
! return STATUS_OK;
! }
! else if (receivepacket->code == RADIUS_ACCESS_REJECT)
! {
! closesocket(sock);
! return STATUS_ERROR;
! }
! else
! {
! ereport(LOG,
! (errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
! receivepacket->code, port->user_name)));
! continue;
! }
! } /* while (true) */
}
*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 2619,2625 **** CheckRADIUSAuth(Port *port)
char portstr[128];
ACCEPT_TYPE_ARG3 addrsize;
fd_set fdset;
! struct timeval timeout;
int i,
r;
--- 2619,2625 ----
char portstr[128];
ACCEPT_TYPE_ARG3 addrsize;
fd_set fdset;
! struct timeval endtime;
int i,
r;
***************
*** 2777,2790 **** CheckRADIUSAuth(Port *port)
/* Don't need the server address anymore */
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
! /* Wait for a response */
! timeout.tv_sec = RADIUS_TIMEOUT;
! timeout.tv_usec = 0;
! FD_ZERO(&fdset);
! FD_SET(sock, &fdset);
while (true)
{
r = select(sock + 1, &fdset, NULL, NULL, &timeout);
if (r < 0)
{
--- 2777,2812 ----
/* Don't need the server address anymore */
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
! /*
! * Figure out at what time we should time out. We can't just use
! * a single call to select() with a timeout, since somebody can
! * be sending invalid packets to our port thus causing us to
! * retry in a loop and never time out.
! */
! gettimeofday(&endtime, NULL);
! endtime.tv_sec += RADIUS_TIMEOUT;
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);
if (r < 0)
{
***************
*** 2805,2815 **** CheckRADIUSAuth(Port *port)
return STATUS_ERROR;
}
! /* else we actually have a packet ready to read */
! break;
! }
- /* Read the response packet */
addrsize = sizeof(remoteaddr);
packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0,
(struct sockaddr *) & remoteaddr, &addrsize);
--- 2827,2843 ----
return STATUS_ERROR;
}
! /*
! * 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.
! */
addrsize = sizeof(remoteaddr);
packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0,
(struct sockaddr *) & remoteaddr, &addrsize);
***************
*** 2817,2828 **** CheckRADIUSAuth(Port *port)
{
ereport(LOG,
(errmsg("could not read RADIUS response: %m")));
- closesocket(sock);
return STATUS_ERROR;
}
- closesocket(sock);
-
#ifdef HAVE_IPV6
if (remoteaddr.sin6_port != htons(port->hba->radiusport))
#else
--- 2845,2853 ----
***************
*** 2838,2851 **** CheckRADIUSAuth(Port *port)
(errmsg("RADIUS response was sent from incorrect port: %i",
ntohs(remoteaddr.sin_port))));
#endif
! return STATUS_ERROR;
}
if (packetlength < RADIUS_HEADER_LENGTH)
{
ereport(LOG,
(errmsg("RADIUS response too short: %i", packetlength)));
! return STATUS_ERROR;
}
if (packetlength != ntohs(receivepacket->length))
--- 2863,2876 ----
(errmsg("RADIUS response was sent from incorrect port: %i",
ntohs(remoteaddr.sin_port))));
#endif
! continue;
}
if (packetlength < RADIUS_HEADER_LENGTH)
{
ereport(LOG,
(errmsg("RADIUS response too short: %i", packetlength)));
! continue;
}
if (packetlength != ntohs(receivepacket->length))
***************
*** 2853,2859 **** CheckRADIUSAuth(Port *port)
ereport(LOG,
(errmsg("RADIUS response has corrupt length: %i (actual length %i)",
ntohs(receivepacket->length), packetlength)));
! return STATUS_ERROR;
}
if (packet->id != receivepacket->id)
--- 2878,2884 ----
ereport(LOG,
(errmsg("RADIUS response has corrupt length: %i (actual length %i)",
ntohs(receivepacket->length), packetlength)));
! continue;
}
if (packet->id != receivepacket->id)
***************
*** 2861,2867 **** CheckRADIUSAuth(Port *port)
ereport(LOG,
(errmsg("RADIUS response is to a different request: %i (should be %i)",
receivepacket->id, packet->id)));
! return STATUS_ERROR;
}
/*
--- 2886,2892 ----
ereport(LOG,
(errmsg("RADIUS response is to a different request: %i (should be %i)",
receivepacket->id, packet->id)));
! continue;
}
/*
***************
*** 2886,2892 **** CheckRADIUSAuth(Port *port)
ereport(LOG,
(errmsg("could not perform MD5 encryption of received packet")));
pfree(cryptvector);
! return STATUS_ERROR;
}
pfree(cryptvector);
--- 2911,2917 ----
ereport(LOG,
(errmsg("could not perform MD5 encryption of received packet")));
pfree(cryptvector);
! continue;
}
pfree(cryptvector);
***************
*** 2894,2911 **** CheckRADIUSAuth(Port *port)
{
ereport(LOG,
(errmsg("RADIUS response has incorrect MD5 signature")));
! return STATUS_ERROR;
}
if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
return STATUS_OK;
else if (receivepacket->code == RADIUS_ACCESS_REJECT)
return STATUS_ERROR;
else
{
ereport(LOG,
(errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
receivepacket->code, port->user_name)));
! return STATUS_ERROR;
}
}
--- 2919,2943 ----
{
ereport(LOG,
(errmsg("RADIUS response has incorrect MD5 signature")));
! continue;
}
if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
+ {
+ closesocket(sock);
return STATUS_OK;
+ }
else if (receivepacket->code == RADIUS_ACCESS_REJECT)
+ {
+ closesocket(sock);
return STATUS_ERROR;
+ }
else
{
ereport(LOG,
(errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
receivepacket->code, port->user_name)));
! continue;
}
+ } /* while (true) */
}
--
Sent via pgsql-bugs mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-bugs