Обсуждение: Change authentication error message (patch)

Поиск
Список
Период
Сортировка

Change authentication error message (patch)

От
"Joshua D. Drake"
Дата:
Hello,

Instead of pushing extra info to the logs I decided that we could 
without giving away extra details per policy. I wrote the error message 
in a way that tells the most obvious problems, without admitting to any 
of them. Please see attached:

diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 415b614..a775534 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -270,7 +270,7 @@ auth_failed(Port *port, int status)                        break;                case uaPassword:
            case uaMD5:
 
-                       errstr = gettext_noop("password authentication 
failed for user \"%s\"");
+                       errstr = gettext_noop("password, username or 
password expiry failed for user \"%s\"");                        /* We use it to indicate if a .pgpass password 
failed. */                        errcode_return = ERRCODE_INVALID_PASSWORD;                        break;



Re: Change authentication error message (patch)

От
Markus Wanner
Дата:
On 06/16/2013 06:02 PM, Joshua D. Drake wrote:
> Instead of pushing extra info to the logs I decided that we could
> without giving away extra details per policy. I wrote the error message
> in a way that tells the most obvious problems, without admitting to any
> of them. Please see attached:

+1 for solving this with a bit of word-smithing.

However, the proposed wording doesn't sound like a full sentence to my
ears, because a password or username cannot fail per-se.

How about:
"password authentication failed or account expired for user \"%s\""

It's a bit longer, but sounds more like a full sentence, no?

Regards

Markus Wanner



Re: Change authentication error message (patch)

От
"Joshua D. Drake"
Дата:
On 06/18/2013 02:25 AM, Markus Wanner wrote:
>
> On 06/16/2013 06:02 PM, Joshua D. Drake wrote:
>> Instead of pushing extra info to the logs I decided that we could
>> without giving away extra details per policy. I wrote the error message
>> in a way that tells the most obvious problems, without admitting to any
>> of them. Please see attached:
>
> +1 for solving this with a bit of word-smithing.
>
> However, the proposed wording doesn't sound like a full sentence to my
> ears, because a password or username cannot fail per-se.

I believe it actually can. The error message that is returned for a bad 
password, bad user or expired password is all the same. Which is why I 
put the username in there.

>
> How about:
> "password authentication failed or account expired for user \"%s\""
>
> It's a bit longer, but sounds more like a full sentence, no?

Yes but I don't think it is accurate, what about:

"Authentication failed or password has expired for user \"%s\""

Authentication failed covers any combination of a username/password 
being wrong and obviously password expired covers the other.

Sincerely,

Joshua D. Drake

>
> Regards
>
> Markus Wanner
>


-- 
Command Prompt, Inc. - http://www.commandprompt.com/  509-416-6579
PostgreSQL Support, Training, Professional Services and Development
High Availability, Oracle Conversion, Postgres-XC, @cmdpromptinc
For my dreams of your image that blossoms   a rose in the deeps of my heart. - W.B. Yeats



Re: Change authentication error message (patch)

От
Markus Wanner
Дата:
This probably is nit-picking, but it interests me in terms of how the
language is used and understood.

On 06/19/2013 08:55 PM, Joshua D. Drake wrote:
> I believe it actually can. The error message that is returned for a bad
> password, bad user or expired password is all the same. Which is why I
> put the username in there.

Sure, the authentication can fail for all these reasons. What I stumbled
over was the formulation of a "failed username". If an engine fails, it
might literally fall apart. The username itself - even if it doesn't
pass authentication - is not falling apart in the same sense. But does
the username (or the password) fail if authentication with it (in
combination with password and account expiration time) is not possible?
After all, it might still a valid and complete username for another
cluster or another service.

You can probably say: "that username failed" when you actually mean it
"failed to authenticate together with the provided password". Or how do
English native speakers perceive this?

> "Authentication failed or password has expired for user \"%s\""
> 
> Authentication failed covers any combination of a username/password
> being wrong and obviously password expired covers the other.

Works for me. Considering the password to be the thing that expires
(rather than the account) is probably more accurate as well.

Regards

Markus Wanner



Re: Change authentication error message (patch)

От
"Joshua D. Drake"
Дата:
On 06/19/2013 01:18 PM, Markus Wanner wrote:

>> "Authentication failed or password has expired for user \"%s\""
>>
>> Authentication failed covers any combination of a username/password
>> being wrong and obviously password expired covers the other.
>
> Works for me. Considering the password to be the thing that expires
> (rather than the account) is probably more accurate as well.

It is also how it is worded in the docs (which is why I used it). Patch 
below.

JD

diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 415b614..f129fe1 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -270,7 +270,7 @@ auth_failed(Port *port, int status)                        break;                case uaPassword:
            case uaMD5:
 
-                       errstr = gettext_noop("password authentication 
failed for user \"%s\"");
+                       errstr = gettext_noop("Authentication failed or 
password has expired for user \"%s\"");                        /* We use it to indicate if a .pgpass password 
failed. */                        errcode_return = ERRCODE_INVALID_PASSWORD;                        break;



-- 
Command Prompt, Inc. - http://www.commandprompt.com/  509-416-6579
PostgreSQL Support, Training, Professional Services and Development
High Availability, Oracle Conversion, Postgres-XC, @cmdpromptinc
For my dreams of your image that blossoms   a rose in the deeps of my heart. - W.B. Yeats



Re: Change authentication error message (patch)

От
Jeff Janes
Дата:
On Wed, Jun 19, 2013 at 11:55 AM, Joshua D. Drake <jd@commandprompt.com> wrote:

On 06/18/2013 02:25 AM, Markus Wanner wrote:

On 06/16/2013 06:02 PM, Joshua D. Drake wrote:


How about:
"password authentication failed or account expired for user \"%s\""

It's a bit longer, but sounds more like a full sentence, no?

Yes but I don't think it is accurate, what about:

"Authentication failed or password has expired for user \"%s\""

I think we need to keep the first "password".  "Password authentication" is a single thing, it is the authentication method attempted.  It is the password method (which includes MD5) which failed, as opposed to the LDAP method or the Peer method or one of the other methods.

Without this level of explicitness, it might be hard to figure out which row in pg_hba.conf was the one that PostgreSQL glommed onto to use for authentication.  (Although by this argument, I don't know why MD5 doesn't get its own message specific to it, rather than sharing plain password)

Cheers,

Jeff

Re: Change authentication error message (patch)

От
Markus Wanner
Дата:
On 06/20/2013 12:51 AM, Jeff Janes wrote:
> I think we need to keep the first "password".  "Password authentication"
> is a single thing, it is the authentication method attempted.  It is the
> password method (which includes MD5) which failed, as opposed to the
> LDAP method or the Peer method or one of the other methods.

That's against the rule of not revealing any more knowledge than a
potential attacker already has, no? For that reason, I'd rather go with
just "authentication failed".

> Without this level of explicitness, it might be hard to figure out which
> row in pg_hba.conf was the one that PostgreSQL glommed onto to use for
> authentication.

As argued before, that should go into the logs for diagnosis by the
sysadmin, but should not be revealed to an attacker.

Regards

Markus Wanner



Re: Change authentication error message (patch)

От
Marko Tiikkaja
Дата:
On 20/06/2013 08:47, Markus Wanner wrote:
> On 06/20/2013 12:51 AM, Jeff Janes wrote:
>> I think we need to keep the first "password".  "Password authentication"
>> is a single thing, it is the authentication method attempted.  It is the
>> password method (which includes MD5) which failed, as opposed to the
>> LDAP method or the Peer method or one of the other methods.
>
> That's against the rule of not revealing any more knowledge than a
> potential attacker already has, no? For that reason, I'd rather go with
> just "authentication failed".

My understanding is that the attacker would already have that 
information since the server would have sent an 
AuthenticationMD5Password message to get to the error in the first 
place.  And we still reveal the authentication method to the frontend in 
all other cases ("peer authentication failed", for example).

>> Without this level of explicitness, it might be hard to figure out which
>> row in pg_hba.conf was the one that PostgreSQL glommed onto to use for
>> authentication.
>
> As argued before, that should go into the logs for diagnosis by the
> sysadmin, but should not be revealed to an attacker.

Isn't the point of this patch exactly that we didn't want to go down 
that road?  I.e. "password authentication failed" didn't say that the 
password might've expired, but some people thought just logging a 
WARNING/LOG wasn't enough.


Regards,
Marko Tiikkaja



Re: Change authentication error message (patch)

От
Markus Wanner
Дата:
On 06/20/2013 12:27 PM, Marko Tiikkaja wrote:
> My understanding is that the attacker would already have that
> information since the server would have sent an
> AuthenticationMD5Password message to get to the error in the first
> place.  And we still reveal the authentication method to the frontend in
> all other cases ("peer authentication failed", for example).

Oh, right, I wasn't aware of that. Never mind, then.

+1 for keeping it mention "password authentication" explicitly.

However, thinking about this a bit more: Other authentication methods
may also provide password (or even account) expiration times. And may
fail to authenticate a user for entirely different reasons.

Given that, I wonder if "password expired" is such a special case worth
mentioning in case of the "password auth" method. If we go down that
path, don't we also have to include "auth server unreachable" as a
possible cause for authentication failure for methods that use an
external server?

Regards

Markus Wanner



Re: Change authentication error message (patch)

От
Bruce Momjian
Дата:
On Wed, Jun 19, 2013 at 01:27:39PM -0700, Joshua D. Drake wrote:
>
> On 06/19/2013 01:18 PM, Markus Wanner wrote:
>
> >>"Authentication failed or password has expired for user \"%s\""
> >>
> >>Authentication failed covers any combination of a username/password
> >>being wrong and obviously password expired covers the other.
> >
> >Works for me. Considering the password to be the thing that expires
> >(rather than the account) is probably more accurate as well.
>
> It is also how it is worded in the docs (which is why I used it).
> Patch below.

I have developed the attached patch to fix this problem.  Do I need to
say "invalid user or invalid or expired password"?
     ---------------

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + Everyone has their own god. +

Вложения

Re: Change authentication error message (patch)

От
Tom Lane
Дата:
Bruce Momjian <bruce@momjian.us> writes:
> I have developed the attached patch to fix this problem.  Do I need to
> say "invalid user or invalid or expired password"?

I'm not convinced that this improves anything.  The problem might not in
fact be either of the things you mention, in which case the new message 
is outright misleading.  Also, what of the policy stated in the header
comment for the function you're hacking, ie we intentionally don't reveal
the precise cause of the failure to the client?
        regards, tom lane



Re: Change authentication error message (patch)

От
Bruce Momjian
Дата:
On Thu, Jan 23, 2014 at 10:39:34PM -0500, Tom Lane wrote:
> Bruce Momjian <bruce@momjian.us> writes:
> > I have developed the attached patch to fix this problem.  Do I need to
> > say "invalid user or invalid or expired password"?
> 
> I'm not convinced that this improves anything.  The problem might not in
> fact be either of the things you mention, in which case the new message 
> is outright misleading.  Also, what of the policy stated in the header
> comment for the function you're hacking, ie we intentionally don't reveal
> the precise cause of the failure to the client?

Well, the only solution then would be to add some weasel words like
"perhaps expired password", but that seems so rare that I doubt it would
apply very often and seems like an odd suggestion.   We could go with:
password authentication failed for user \"%s\": perhaps invalid or expired password

We did have two threads on this issue in the past 12 months so I figured
we should try to do something.

--  Bruce Momjian  <bruce@momjian.us>        http://momjian.us EnterpriseDB
http://enterprisedb.com
 + Everyone has their own god. +



Re: Change authentication error message (patch)

От
Tom Lane
Дата:
Bruce Momjian <bruce@momjian.us> writes:
> On Thu, Jan 23, 2014 at 10:39:34PM -0500, Tom Lane wrote:
>> I'm not convinced that this improves anything.  The problem might not in
>> fact be either of the things you mention, in which case the new message 
>> is outright misleading.  Also, what of the policy stated in the header
>> comment for the function you're hacking, ie we intentionally don't reveal
>> the precise cause of the failure to the client?

> Well, the only solution then would be to add some weasel words like
> "perhaps expired password", but that seems so rare that I doubt it would
> apply very often and seems like an odd suggestion.   We could go with:

>     password authentication failed for user \"%s\": perhaps invalid or expired password

> We did have two threads on this issue in the past 12 months so I figured
> we should try to do something.

I agree with doing *something*, but this particular thing seems to violate
our very long-standing policy on how to deal with authentication failures,
as well as being too vague to be really useful.

What would be well within that policy is to log additional information
into the postmaster log.  I see that md5_crypt_verify knows perfectly
well whether the problem is no password set, wrong password, or expired
password.  I don't see anything wrong with having it emit a log entry
--- maybe not in the second case for fear of log-spam complaints, but
certainly the third case and maybe the first one.  Or possibly cleaner,
have it return additional status so that auth_failed() can include the
info in the main ereport using errdetail_log().
        regards, tom lane



Re: Change authentication error message (patch)

От
Tom Lane
Дата:
I wrote:
> I agree with doing *something*, but this particular thing seems to violate
> our very long-standing policy on how to deal with authentication failures,
> as well as being too vague to be really useful.

> What would be well within that policy is to log additional information
> into the postmaster log.  I see that md5_crypt_verify knows perfectly
> well whether the problem is no password set, wrong password, or expired
> password.  I don't see anything wrong with having it emit a log entry
> --- maybe not in the second case for fear of log-spam complaints, but
> certainly the third case and maybe the first one.  Or possibly cleaner,
> have it return additional status so that auth_failed() can include the
> info in the main ereport using errdetail_log().

Attached is a proposed patch that does exactly that.  This just addresses
the cases mentioned above; once the infrastructure is in place, there
might be more things that would be worth logging using this mechanism.

            regards, tom lane

diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 882dc8f..1974b57 100644
*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 38,46 ****
   *----------------------------------------------------------------
   */
  static void sendAuthRequest(Port *port, AuthRequest areq);
! static void auth_failed(Port *port, int status);
  static char *recv_password_packet(Port *port);
! static int    recv_and_check_password_packet(Port *port);


  /*----------------------------------------------------------------
--- 38,46 ----
   *----------------------------------------------------------------
   */
  static void sendAuthRequest(Port *port, AuthRequest areq);
! static void auth_failed(Port *port, int status, char *logdetail);
  static char *recv_password_packet(Port *port);
! static int    recv_and_check_password_packet(Port *port, char **logdetail);


  /*----------------------------------------------------------------
*************** ClientAuthentication_hook_type ClientAut
*** 207,216 ****
   * in use, and these are items that must be presumed known to an attacker
   * anyway.
   * Note that many sorts of failure report additional information in the
!  * postmaster log, which we hope is only readable by good guys.
   */
  static void
! auth_failed(Port *port, int status)
  {
      const char *errstr;
      int            errcode_return = ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION;
--- 207,217 ----
   * in use, and these are items that must be presumed known to an attacker
   * anyway.
   * Note that many sorts of failure report additional information in the
!  * postmaster log, which we hope is only readable by good guys.  In
!  * particular, if logdetail isn't NULL, we send that string to the log.
   */
  static void
! auth_failed(Port *port, int status, char *logdetail)
  {
      const char *errstr;
      int            errcode_return = ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION;
*************** auth_failed(Port *port, int status)
*** 273,286 ****
      }

      if (port->hba)
!         ereport(FATAL,
!                 (errcode(errcode_return),
!                  errmsg(errstr, port->user_name),
!                  errdetail_log("Connection matched pg_hba.conf line %d: \"%s\"", port->hba->linenumber,
port->hba->rawline)));
!     else
!         ereport(FATAL,
!                 (errcode(errcode_return),
!                  errmsg(errstr, port->user_name)));

      /* doesn't return */
  }
--- 274,294 ----
      }

      if (port->hba)
!     {
!         char   *cdetail;
!
!         cdetail = psprintf(_("Connection matched pg_hba.conf line %d: \"%s\""),
!                            port->hba->linenumber, port->hba->rawline);
!         if (logdetail)
!             logdetail = psprintf("%s\n%s", logdetail, cdetail);
!         else
!             logdetail = cdetail;
!     }
!
!     ereport(FATAL,
!             (errcode(errcode_return),
!              errmsg(errstr, port->user_name),
!              logdetail ? errdetail_log("%s", logdetail) : 0));

      /* doesn't return */
  }
*************** void
*** 294,299 ****
--- 302,308 ----
  ClientAuthentication(Port *port)
  {
      int            status = STATUS_ERROR;
+     char       *logdetail = NULL;

      /*
       * Get the authentication method to use for this frontend/database
*************** ClientAuthentication(Port *port)
*** 507,518 ****
                          (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                           errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
              sendAuthRequest(port, AUTH_REQ_MD5);
!             status = recv_and_check_password_packet(port);
              break;

          case uaPassword:
              sendAuthRequest(port, AUTH_REQ_PASSWORD);
!             status = recv_and_check_password_packet(port);
              break;

          case uaPAM:
--- 516,527 ----
                          (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                           errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
              sendAuthRequest(port, AUTH_REQ_MD5);
!             status = recv_and_check_password_packet(port, &logdetail);
              break;

          case uaPassword:
              sendAuthRequest(port, AUTH_REQ_PASSWORD);
!             status = recv_and_check_password_packet(port, &logdetail);
              break;

          case uaPAM:
*************** ClientAuthentication(Port *port)
*** 552,558 ****
      if (status == STATUS_OK)
          sendAuthRequest(port, AUTH_REQ_OK);
      else
!         auth_failed(port, status);

      /* Done with authentication, so we should turn off immediate interrupts */
      ImmediateInterruptOK = false;
--- 561,567 ----
      if (status == STATUS_OK)
          sendAuthRequest(port, AUTH_REQ_OK);
      else
!         auth_failed(port, status, logdetail);

      /* Done with authentication, so we should turn off immediate interrupts */
      ImmediateInterruptOK = false;
*************** recv_password_packet(Port *port)
*** 680,688 ****
  /*
   * Called when we have sent an authorization request for a password.
   * Get the response and check it.
   */
  static int
! recv_and_check_password_packet(Port *port)
  {
      char       *passwd;
      int            result;
--- 689,698 ----
  /*
   * Called when we have sent an authorization request for a password.
   * Get the response and check it.
+  * On error, optionally store a detail string at *logdetail.
   */
  static int
! recv_and_check_password_packet(Port *port, char **logdetail)
  {
      char       *passwd;
      int            result;
*************** recv_and_check_password_packet(Port *por
*** 692,698 ****
      if (passwd == NULL)
          return STATUS_EOF;        /* client wouldn't send password */

!     result = md5_crypt_verify(port, port->user_name, passwd);

      pfree(passwd);

--- 702,708 ----
      if (passwd == NULL)
          return STATUS_EOF;        /* client wouldn't send password */

!     result = md5_crypt_verify(port, port->user_name, passwd, logdetail);

      pfree(passwd);

diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 56b3ea8..5451db6 100644
*** a/src/backend/libpq/crypt.c
--- b/src/backend/libpq/crypt.c
***************
*** 29,36 ****
  #include "utils/timestamp.h"


  int
! md5_crypt_verify(const Port *port, const char *role, char *client_pass)
  {
      int            retval = STATUS_ERROR;
      char       *shadow_pass,
--- 29,42 ----
  #include "utils/timestamp.h"


+ /*
+  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+  * In the error case, optionally store a palloc'd string at *logdetail
+  * that will be sent to the postmaster log (but not the client).
+  */
  int
! md5_crypt_verify(const Port *port, const char *role, char *client_pass,
!                  char **logdetail)
  {
      int            retval = STATUS_ERROR;
      char       *shadow_pass,
*************** md5_crypt_verify(const Port *port, const
*** 58,63 ****
--- 64,71 ----
      if (isnull)
      {
          ReleaseSysCache(roleTup);
+         *logdetail = psprintf(_("User \"%s\" has no password assigned."),
+                               role);
          return STATUS_ERROR;    /* user has no password */
      }
      shadow_pass = TextDatumGetCString(datum);
*************** md5_crypt_verify(const Port *port, const
*** 148,154 ****
--- 156,166 ----
          if (isnull)
              retval = STATUS_OK;
          else if (vuntil < GetCurrentTimestamp())
+         {
+             *logdetail = psprintf(_("User \"%s\" has an expired password."),
+                                   role);
              retval = STATUS_ERROR;
+         }
          else
              retval = STATUS_OK;
      }
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 818e57e..b91024f 100644
*** a/src/include/libpq/crypt.h
--- b/src/include/libpq/crypt.h
***************
*** 15,21 ****

  #include "libpq/libpq-be.h"

! extern int md5_crypt_verify(const Port *port, const char *user,
!                  char *client_pass);

  #endif
--- 15,21 ----

  #include "libpq/libpq-be.h"

! extern int md5_crypt_verify(const Port *port, const char *role,
!                  char *client_pass, char **logdetail);

  #endif

Re: Change authentication error message (patch)

От
Bruce Momjian
Дата:
On Fri, Jan 24, 2014 at 10:10:00AM -0500, Tom Lane wrote:
> Bruce Momjian <bruce@momjian.us> writes:
> > On Thu, Jan 23, 2014 at 10:39:34PM -0500, Tom Lane wrote:
> >> I'm not convinced that this improves anything.  The problem might not in
> >> fact be either of the things you mention, in which case the new message 
> >> is outright misleading.  Also, what of the policy stated in the header
> >> comment for the function you're hacking, ie we intentionally don't reveal
> >> the precise cause of the failure to the client?
> 
> > Well, the only solution then would be to add some weasel words like
> > "perhaps expired password", but that seems so rare that I doubt it would
> > apply very often and seems like an odd suggestion.   We could go with:
> 
> >     password authentication failed for user \"%s\": perhaps invalid or expired password
> 
> > We did have two threads on this issue in the past 12 months so I figured
> > we should try to do something.
> 
> I agree with doing *something*, but this particular thing seems to violate
> our very long-standing policy on how to deal with authentication failures,
> as well as being too vague to be really useful.
> 
> What would be well within that policy is to log additional information
> into the postmaster log.  I see that md5_crypt_verify knows perfectly
> well whether the problem is no password set, wrong password, or expired
> password.  I don't see anything wrong with having it emit a log entry
> --- maybe not in the second case for fear of log-spam complaints, but
> certainly the third case and maybe the first one.  Or possibly cleaner,
> have it return additional status so that auth_failed() can include the
> info in the main ereport using errdetail_log().

I was afraid that PGOPTIONS='-c client_min_messages=log' would allow
clients to see the log messages, but in testing I found we don't show
them during authentication, and I found this C comment:
        * client_min_messages is honored only after we complete the        * authentication handshake.  This is
requiredboth for security        * reasons and because many clients can't handle NOTICE messages        * during
authentication.

I like the 'LOG' idea very much, and liked your patch too.  :-)

--  Bruce Momjian  <bruce@momjian.us>        http://momjian.us EnterpriseDB
http://enterprisedb.com
 + Everyone has their own god. +