[Twisted-Python] LDAP binds appears to succeed with no password

Peter Westlake peter.westlake at pobox.com
Fri Jul 30 09:31:00 EDT 2010


On Fri, 30 Jul 2010 13:55 +0200, "Alan Franzoni" <mailing at franzoni.eu>
wrote:
> On 7/30/10 12:54 PM, Peter Westlake wrote:
> > I'm using LDAP to authenticate users, and when I give it an empty
> > password, it appears to succeed! Can anyone see what I'm doing wrong?
> > I've added comments by the log messages that appear in the output.
> 
> Please post the full code, including how you're using such class. Also:

The rest of it is below.

> - log the entry you've found and that you're using to re-bind. It might
> not be what you're expecting.

It is, though - it's my directory entry. See the output, below.

> - try the very same query using something like ldapsearch or Apache LDAP
> Studio, and see whether the result differs.

With ldapsearch, it fails as expected. That's a bit different to my
code, though, because it only does a bind and a search. It isn't
trying to re-bind using the entry it finds.

LDAP Studio is a great find, thank you! And thank you for your help.

Peter.

Here's the test program:
-----------------------------------------------------------------------
auth.py:

from lda import LDAPAuthenticator

import getpass
from twisted.internet import reactor

import log

username = raw_input('Username: ')
password = getpass.getpass('Password: ')


def _success(entry):
    log.info('Successful authentication as %r' % username)
    return username

def _failure(f):
    log.info('Failed authentication as %r' % username, f)
    raise Exception('Authentication failed')

def stop(result):
    print 'Result', result
    reactor.stop()

authenticator= LDAPAuthenticator(
    hostname=LDAP_HOST,
    base_dn=LDAP_ROOT,
    attr='sAMAccountName',
    bind_dn=LDAP_BIND_DN,
    bind_pw=LDAP_BIND_PW
    )
try:
    print 'Before authenticate'
    d = authenticator.authenticate(username, password)
    print 'Called authenticate'
except Exception, e:
    print 'Exception was:'
    print e
    reactor.stop()

d.addCallbacks(_success, _failure)
d.addBoth(stop)

reactor.run()

--------------------------------------------------------------------------------
log.py:

# Simplified log for test purposes.

def info(*message):
    print message

def debug(*message):
    print message

def rep(e, *message):
    print message
    print 'Exception is:', e
-------------------------------------------------------------------------------
The output of the program:

$ python auth.py
Username: peterw
Password:
Before authenticate
('Empty password!!!!',)
Called authenticate
('Empty password',)
('Succeeded with an empty password!',)
('user_entry:',)
(LDAPEntryWithClient(dn='CN=Peter Westlake,OU=....',
attributes={'sAMAccountName':
JournaledLDAPAttributeSet('sAMAccountName', ['peterw'])}),)
('Login:', 'peterw')
("Successful authentication as 'peterw'",)
Result peterw
---------------------------------------------------------------------------------
The file I posted last time, for completeness and ease of reference:

lda.py:

from twisted.internet import reactor, defer
from ldaptor.protocols.ldap import ldapclient, ldapsyntax,
ldapconnector, ldaperrors
from ldaptor.protocols import pureldap as L

import log

class LDAPAuthenticator(object):
    def __init__(self, base_dn, hostname=None, attr='uid', bind_dn='',
                 bind_pw='', filter=L.LDAPFilter_present('cn')):
        self.hostname = hostname
        self.base_dn = base_dn
        self.attr = attr
        self.bind_dn = bind_dn
        self.bind_pw = bind_pw
        self.filter = filter
        self.cli = None

    @defer.inlineCallbacks
    def authenticate(self, username, password):
        if not password:
            log.info('Empty password!!!!')
        else:
            log.debug('Attempting to login as', username)

        c = ldapconnector.LDAPClientCreator(reactor,
        ldapclient.LDAPClient)
        self.client = yield c.connect(self.base_dn)

        yield ldapsyntax.LDAPEntry(self.client,
        self.bind_dn).bind(self.bind_pw)

        entries = []
        base = ldapsyntax.LDAPEntry(self.client, self.base_dn)

        yield base.search(
            filterObject=L.LDAPFilter_and(
                [L.LDAPFilter_equalityMatch(
                        attributeDesc=L.LDAPAttributeDescription(self.attr),
                        assertionValue=L.LDAPAssertionValue(username)
                    ),
                 self.filter,
                 ]),
            attributes = (self.attr,),  # No need to read the whole
            entry!
            callback=entries.append
        )
        n_entries = len(entries)
        if n_entries == 0:
            self.client.unbind()
            log.debug('Failed login for %s: no search results' %
            (username,))
            raise Exception('No search results!')
        elif n_entries > 1:
            self.client.unbind()
            log.debug('Failed login as %s: %d search results for unique
            entry!' % (username, n_entries))
            raise Exception('%d search results for unique entry!' %
            n_entries)
        else:
            # The password matches if we can bind as this DN with it.
            try:
                if not password:
                    log.info('Empty password')
                user_entry = yield entries[0].bind(password)
                if not password:
                    log.info('Succeeded with an empty password!')
                    log.info('user_entry:')
                    log.info(user_entry)
                log.info('Login:', username)
            except ldaperrors.LDAPInvalidCredentials:
                log.info('Failed login for %s: invalid credentials' %
                username)
                raise
            except Exception, e:
                log.rep(e, 'Error while binding with user password')
                self.client.unbind()
                raise
            self.client.unbind()
            defer.returnValue(user_entry)
--------END-----------



More information about the Twisted-Python mailing list