Arcath / Adauth

A Ruby interface for Microsoft's Active Directory based off ruby-net-ldap
http://adauth.arcath.net
MIT License
174 stars 29 forks source link

passwordless login! #37

Closed weightyfoe closed 11 years ago

weightyfoe commented 11 years ago

We've noticed that it's possible to login using adauth by supplying a correct username & a blank password. We've seen the following:

correct username & correct password -> login correct username & incorrect password -> fail correct username, & blank password -> login (as username) incorrect username & [correct|incorrect|blank] password -> fail

This is even for users who are NOT currently in the users table (ie have never previously logged in).

Currently using the latest git master branch (pulled from git today using bundle update): https://github.com/Arcath/Adauth.git

Setup is like this (boring bits omitted):

initializers/adauth.rb
c.doman = "domain.local"
c.query_user = "<a low privilege account>"
c.query_password = "****"
c.server = "10.0.0.1"
c.base = "dc=domain, dc=local"
models/user.rb
class User < ActiveRecord::Base

include Adauth::Rails::ModelBridge

AdauthMappings = {
    :login => :login,
    :group_strings => :cn_groups,
    :ou_strings => :dn_ous,
    :email => :email,
    :name => :name
}

AdauthSearchField = [:login, :login]
controllers/sessions_controller.rb

def create
   begin
        ldap_user = Adauth.authenticate(params[:username], params[:password])
        if ldap_user
            user = User.return_and_create_from_adauth(ldap_user)
            session[:user_id] = user.id
            redirect_to <somewhere nice> and return
        else
            redirect_to <a bad place> and return
        end
    rescue
        redirect_to <an even worse place> and return
    end
end

def new
    redirect_to root_path if current_user
end

We added the begin/rescue/end in the create session because we encountered an error when giving incorrect usernames/passwords (ldap_user was true even for bad usernames - like logging in as asdf/crap_pass)

Removing the rescue still lets you log in without a password, and errors if the username or password is incorrect (but not blank), ie it being there has no effect on this issue, but it does prevent an error.

What's going on?

Oh, an excerpt from the users table:

id: 18
login: james
group_strings: --- - !ruby/string:Net::BER::BerIdentifiedString |-   U1NMIFZQTiBVc2Vycw== - !ruby/string:Net::BER::BerIdentifiedString |-   QXBwX1NoYXJlcG9pbnRfS25vd2xlZGdlVXBkYXRlcnM= - !ruby/string:Net::BER::BerIdentifiedString |-   RGVwdF9JVA== - !ruby/string:Net::BER::BerIdentifiedString |-   RXZlcnlvbmUgT3V0bG9vaw== - !ruby/string:Net::BER::BerIdentifiedString |-   RG9tYWluIEFkbWlucw== - !ruby/string:Net::BER::BerIdentifiedString |-   RW50ZXJwcmlzZSBBZG1pbnM= 
name: James
ou_strings: --- - Users - IT - Department - CompanyHO 
email: james@domain.com

Not sure whats going on with group_strings(!) or ou_strings, but I don't currently use them for anything. This user logged on by providing username "james" and no password

Arcath commented 11 years ago

This is very odd.

I threw a quick test together which looks like this:

ldap_user = Adauth.authenticate("administrator", "")
ldap_user.should be_false

which passed no problem. then after adding a few more cases to to the test that first example failed giving me a successful authentication as administrator with no code change or anything...

Something very odd is going on here its almost as if AD is allowing password less bindings which as far as I'm aware it shouldn't.

More research is needed

weightyfoe commented 11 years ago

Is there anything I can do over here to provide some more data? AD config variables, run some ruby code?

You'll have to be be precise about what you want me to do though - I don't really know much at all about AD & it's configuration. Glad to help though, especially if it is something our AD is doing wrong (passwordless logins! Sounds bad...)

weightyfoe commented 11 years ago

Here's the log of a single login attempt, using username "james" and no password:

# Logfile created on 2013-08-16 11:58:31 +0100 by logger.rb/36483
Loading new config
Attempting to authenticate as james
Searching for all "(objectClass=user)" where sAMAccountName = james
Connecting to AD as "low_priv_user"
Searching for all "(objectClass=group)" where name = SSL VPN Users
Searching for all "(objectClass=user)" where sAMAccountName = 
Searching for all "(objectClass=group)" where sAMAccountName = 
Searching for all "(objectClass=user)" where sAMAccountName = ITIPAD
Searching for all "(objectClass=group)" where sAMAccountName = ITIPAD
Searching for all "(objectClass=group)" where name = App_Sharepoint_KnowledgeUpdaters
Searching for all "(objectClass=user)" where sAMAccountName = 
Searching for all "(objectClass=group)" where sAMAccountName = 
Searching for all "(objectClass=user)" where sAMAccountName = James Bhatt
Searching for all "(objectClass=group)" where sAMAccountName = James Bhatt
Searching for all "(objectClass=group)" where name = Dept_IT
Searching for all "(objectClass=user)" where sAMAccountName = 
Searching for all "(objectClass=group)" where sAMAccountName = 
Searching for all "(objectClass=user)" where sAMAccountName = User1
Searching for all "(objectClass=group)" where sAMAccountName = User1
Searching for all "(objectClass=group)" where name = Everyone Outlook
Searching for all "(objectClass=user)" where sAMAccountName = 
Searching for all "(objectClass=group)" where sAMAccountName = 
Searching for all "(objectClass=user)" where sAMAccountName = User1
Searching for all "(objectClass=group)" where sAMAccountName = User1
Searching for all "(objectClass=group)" where name = Domain Admins
Searching for all "(objectClass=user)" where sAMAccountName = 
Searching for all "(objectClass=group)" where sAMAccountName = 
Searching for all "(objectClass=user)" where sAMAccountName = SQLEngine Original
Searching for all "(objectClass=group)" where sAMAccountName = SQLEngine Original
Searching for all "(objectClass=group)" where name = Enterprise Admins
Searching for all "(objectClass=user)" where sAMAccountName = 
Searching for all "(objectClass=group)" where sAMAccountName = 
Searching for all "(objectClass=user)" where sAMAccountName = User2
Searching for all "(objectClass=group)" where sAMAccountName = User2
Authentication succesful

I've left my name in, but bodged out the others & replaced them with generic peeps.

weightyfoe commented 11 years ago

Just a question.

Is the binding bit done by this user (set in adauth.rb)?

c.query_user = "<a low privilege account>"
c.query_password = "****"

Because this user is a real user on our AD, ie I would be able to log in as this user & be authenticated. Or, does it use the username & password provided on the login page?

Because in the sessions controller I use (as per the doc):

ldap_user = Adauth.authenticate(params[:username], params[:password])

Which would be the username & password that are typed into the login form correct? So what is the user defined in adauth.rb for?

weightyfoe commented 11 years ago

Ok,

So I create a brand new AD account (called special). The account is bog standard: primary group is Active Directory/users

I can still "Authenticate" a user without using a password, if I connect using the above account as the query user.

I'm unsure of the internals of how AD/LDAP queries actually work, but could this possibly be something like:

1) Connect as a given user (a query user used to make all queries against AD) to create a "connection" to AD 2) Using that connection, ask AD: "Does a user with user name 'foo' and password '' exist?" 3) AD seems to allow an authenticated user to query the directory, so it says yes, there is a user whose username = 'foo' (and because no password is specified, AD doesn't check it) 3a) Same query, but this time using a bad password: Ad says no, because although the user 'foo' exists, the password hashes (which it now uses because it is specified) do not match.

So, is it a case the because we're already connected to AD using the query_user, we're not "authenticating" the user who is trying to log in, we're just trying to see whether they exist or not?

And, could this be fixed by running the query using the supplied username & password rather than using a defined query_user?

Arcath commented 11 years ago

Query User us used to let Adauth query for information e.g. find the groups user X is in.

Authenticate creates a new connection to AD using the supplied credentials to make sure that the details are valid. As far as I'm aware the only way Adauth would do this is if Net/LDAP returned a valid connection and again that should only happen if it gets a successful connection

I've got some time today to test this so hopefully a solution will exist soon (Now that I've managed to replicate the issue my boss is happy for me to spend time looking at instead of doing other work)

Arcath commented 11 years ago

I've written up the case here:

http://arcath.github.io/blog/2013/08/28/issue-37/

Basically the LDAPv3 Specification states that you need to make the rootDSE accessible anonymously which because we bind against it meant that Adauth would get a false positive.