Thread

  1. chkpass Major Issue - compares 'contains' and not 'equal'

    Eyedia Tech <eyedia@debjyoti.com> — 2018-06-07T12:41:44Z

    To replicate use this:
    
    create table "user" (uname text, password chkpass);
    insert into "user" values ('user1', 'password')
    select * from "user" where uname = 'user1' and password = 'password1'
    
    This is a major issue.
    
    Thanks,
    Debjyoti Das
    
  2. Re: chkpass Major Issue - compares 'contains' and not 'equal'

    David G. Johnston <david.g.johnston@gmail.com> — 2018-06-07T14:09:24Z

    On Thursday, June 7, 2018, Eyedia Tech <eyedia@debjyoti.com> wrote:
    
    > To replicate use this:
    >
    > create table "user" (uname text, password chkpass);
    > insert into "user" values ('user1', 'password')
    > select * from "user" where uname = 'user1' and password = 'password1'
    >
    > This is a major issue.
    >
    
    It is also a documented limitation.
    
    The encryption uses the standard Unix function crypt(), and so it suffers
    from all the usual limitations of that function; notably that only the
    first eight characters of a password are considered.
    
    https://www.postgresql.org/docs/10/static/chkpass.html
    
    At this point I'd consider its presence here for backward compatibility
    only and as such the behavior is not something that is likely to be changed.
    
    David J.
    
  3. Re: chkpass Major Issue - compares 'contains' and not 'equal'

    Tom Lane <tgl@sss.pgh.pa.us> — 2018-06-07T14:14:54Z

    Eyedia Tech <eyedia@debjyoti.com> writes:
    > To replicate use this:
    > create table "user" (uname text, password chkpass);
    > insert into "user" values ('user1', 'password')
    > select * from "user" where uname = 'user1' and password = 'password1'
    
    That would depend on the behavior of your local version of crypt(3),
    but historically, that library function uses only the first eight
    characters of the password, which explains this example.
    
    > This is a major issue.
    
    We're not going to treat it as such.  It's clearly documented, see
    
    https://www.postgresql.org/docs/current/static/chkpass.html
    
    FWIW, chkpass is deprecated and has been removed entirely as of PG 11.
    It has a lot of design problems above and beyond its reliance on a
    40-year-old encryption spec.
    
    			regards, tom lane
    
    
    
  4. Re: chkpass Major Issue - compares 'contains' and not 'equal'

    D'Arcy Cain <darcy@druid.net> — 2018-06-07T18:20:00Z

    On 2018-06-07 10:09 AM, David G. Johnston wrote:
    > It is also a documented limitation.
    > 
    > The encryption uses the standard Unix function |crypt()|, and so it
    > suffers from all the usual limitations of that function; notably that
    > only the first eight characters of a password are considered.
    > 
    > https://www.postgresql.org/docs/10/static/chkpass.html
    > 
    > At this point I'd consider its presence here for backward compatibility
    > only and as such the behavior is not something that is likely to be changed.
    
    I have a new version that does MD5 but I haven't had time to work out a
    patch yet.  Here it is if someone wants to update the in-tree version.
    /*
     * PostgreSQL type definitions for chkpass
     * Written by D'Arcy J.M. Cain
     * darcy@druid.net
     * http://www.druid.net/darcy/
     *
     * $Id: chkpass.c 8899 2015-01-01 22:51:54Z darcy $
     * best viewed with tabs set to 4
     */
    
    #include "postgres.h"
    
    #include <time.h>
    #include <unistd.h>
    #ifdef HAVE_CRYPT_H
    #include <crypt.h>
    #endif
    
    #include "fmgr.h"
    #include "utils/guc.h"
    
    PG_MODULE_MAGIC;
    
    void _PG_init(void);
    extern text *cstring_to_text(const char *s);
    
    /*
     * This type encrypts its input unless the first character is a colon.
     * The output is the encrypted form with a leading colon.  The output
     * format is designed to allow dump and reload operations to work as
     * expected without doing special tricks.
     */
    
    
    /*
     * CHKPASS is now a variable-width type. The format for an md5-format
     * password is $1$<salt>$<hash> where the hash is always 22 characters
     * when base-64 encoded. We allocate space for that, plus a leading :
     * plus a trailing null.
     */
    
    typedef struct varlena chkpass;
    #define SALTLEN 8
    #define CHKPASSLEN (VARHDRSZ + SALTLEN + 6 + 22)
    
    /* controlled by the GUC chkpass.use_md5 */
    static bool use_md5 = false;
    
    /*
     * Various forward declarations:
     */
    
    Datum        chkpass_in(PG_FUNCTION_ARGS);
    Datum        chkpass_out(PG_FUNCTION_ARGS);
    Datum        chkpass_rout(PG_FUNCTION_ARGS);
    
    /* Only equal or not equal make sense */
    Datum        chkpass_eq(PG_FUNCTION_ARGS);
    Datum        chkpass_ne(PG_FUNCTION_ARGS);
    
    
    /* This function checks that the password is a good one
     * It's just a placeholder for now */
    static int
    verify_pass(const char *str)
    {
        return 0;
    }
    
    /*
     * CHKPASS reader.
     */
    PG_FUNCTION_INFO_V1(chkpass_in);
    Datum
    chkpass_in(PG_FUNCTION_ARGS)
    {
        char       *str = PG_GETARG_CSTRING(0);
        chkpass    *result;
        char        mysalt[SALTLEN+6];
        int         i;
        static char salt_chars[] =
        "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    
        result = (chkpass *) palloc(CHKPASSLEN);
        /* special case to let us enter encrypted passwords */
        if (*str == ':')
        {
            SET_VARSIZE(result, VARHDRSZ + strlen(str)-1);
            memcpy(VARDATA(result), str+1, VARSIZE(result)-VARHDRSZ);
            PG_RETURN_POINTER(result);
        }
    
        if (verify_pass(str) != 0)
            ereport(ERROR,
                (errcode(ERRCODE_DATA_EXCEPTION),
                errmsg("password \"%s\" is weak", str)));
    
        if (use_md5)
        {
            memcpy(mysalt, "$1$", 3);
            for (i = 3; i < (3+SALTLEN); i++)
                mysalt[i] = salt_chars[random() & 0x3f];
            mysalt[3+SALTLEN] = '$';
            mysalt[4+SALTLEN] = 0;
        }
        else
        {
            mysalt[0] = salt_chars[random() & 0x3f];
            mysalt[1] = salt_chars[random() & 0x3f];
            mysalt[2] = 0;    /* technically the terminator is not necessary
                               * but I like to play safe */
        }
        strcpy(result->vl_dat, crypt(str, mysalt));
        SET_VARSIZE(result, VARHDRSZ + strlen(result->vl_dat));
        PG_RETURN_POINTER(result);
    }
    
    /*
     * CHKPASS output function.
     */
    
    /* common output code */
    static char *
    out(chkpass *password)
    {
        char       *str;
        size_t      sz;
    
        sz = VARSIZE(password) - VARHDRSZ;
        str = palloc(sz + 1);
        memcpy(str, VARDATA(password), sz);
        str[sz] = 0;
        return str;
    }
    
    PG_FUNCTION_INFO_V1(chkpass_out);
    Datum
    chkpass_out(PG_FUNCTION_ARGS)
    {
        char       *str, *result;
    
        str = out((chkpass *) PG_GETARG_POINTER(0));
        result = (char *) palloc(strlen(str) + 2);
        result[0] = ':';
        strcpy(result + 1, str);
    
        PG_RETURN_CSTRING(result);
    }
    
    PG_FUNCTION_INFO_V1(chkpass_rout);
    Datum
    chkpass_rout(PG_FUNCTION_ARGS)
    {
        char       *str;
    
        str = out((chkpass *) PG_GETARG_POINTER(0));
        PG_RETURN_TEXT_P(cstring_to_text(str));
    }
    
    
    /*
     * special output function that doesn't output the colon
     */
    
    /*
     * Boolean tests
     */
    
    PG_FUNCTION_INFO_V1(chkpass_eq);
    Datum
    chkpass_eq(PG_FUNCTION_ARGS)
    {
        chkpass    *a1 = (chkpass *) PG_GETARG_POINTER(0);
        text       *a2 = (text *) PG_GETARG_TEXT_P(1);
        char       *str;
        char       *t;
    
        str = palloc(VARSIZE(a2)-VARHDRSZ+1);
        memcpy(str, VARDATA(a2), VARSIZE(a2)-VARHDRSZ);
        str[VARSIZE(a2)-VARHDRSZ] = 0;
        t = crypt(str, VARDATA(a1));
    
        PG_RETURN_BOOL(memcmp(VARDATA(a1), t, VARSIZE(a1)-VARHDRSZ) == 0);
    }
    
    PG_FUNCTION_INFO_V1(chkpass_ne);
    Datum
    chkpass_ne(PG_FUNCTION_ARGS)
    {
        chkpass    *a1 = (chkpass *) PG_GETARG_POINTER(0);
        text       *a2 = (text *) PG_GETARG_TEXT_P(1);
        char       *str;
        char       *t;
    
        str = palloc(VARSIZE(a2)-VARHDRSZ+1);
        memcpy(str, VARDATA(a2), VARSIZE(a2)-VARHDRSZ);
        str[VARSIZE(a2)-VARHDRSZ] = 0;
        t = crypt(str, a1->vl_dat);
    
        PG_RETURN_BOOL(memcmp(VARDATA(a1), t, VARSIZE(a1)-VARHDRSZ) != 0);
    }
    
    /* define a custom variable to control md5 hashing of passwords.
     * This should require a SIGHUP because it would be administratively
     * disasterous to allow users to turn on md5 hashing, however I get
     * errors when loading the library if I use any setting other than
     * PGC_USERSET */
    void
    _PG_init(void)
    {
        static char desc[] = "Whether to use md5 hashing for chkpass
    passwords.";
        static char longdesc[] = "If set to true, new passwords will be
    hashed using an md5-based algorithm. Otherwise the traditional Unix
    algorithm will be used. Either kind of password can be read, provided
    the server OS supports it.";
    
        DefineCustomBoolVariable(
            "chkpass.use_md5",  /* const char * name */
             desc,              /* const char * short_desc */
             longdesc,          /* const char * long_desc */
             &use_md5,          /* bool * valueAddr */
             true,              /* bool bootValue */
             PGC_USERSET,       /* GucContext context */
             0,                 /* int flags */
             NULL,              /* GucBoolCheckHook check_hook */
             NULL,              /* GucBoolAssignHook assign_hook */
             NULL               /* GucShowHook show_hook */
        );
    }
    
    
    -- 
    D'Arcy J.M. Cain <darcy@druid.net>         |  Democracy is three wolves
    http://www.druid.net/darcy/                |  and a sheep voting on
    +1 416 788 2246     (DoD#0082)    (eNTP)   |  what's for dinner.
    IM: darcy@Vex.Net, VoIP: sip:darcy@druid.net