Check user credentials in LDAP/active directory with NTLM

489 Views Asked by At

I have an apache PHP server in which I am trying to implement permissions using an existing active directory.

We choosed NTLM to authenticate windows users automatically, and that part seems to be working, I am retrieving an username and an answer to the NTLM challenge.

I am communicating (with a service account) with the active directory using PHP ldap functions, and so far I can retrieve users groups and check if usernames exists, but I can't connect as the user to verify the password : $success = @ldap_bind($ad, "$username@$domain}", $password) or die('Could not bind to AD.');

Here I can connect with the cleartext password but only have the so called 'NTLM Response Security Buffer'.

Is it possible to chain NTLM and LDAP, and how so ? Is the PHP ldap_bind simply not supporting NTLM ?


I read here one could transmit both the challenge and the user NTLM Response as a "NETLOGON_NETWORK_INFO", but I cant find any documentation on the way to do so.

I also found a laravel library claiming to do it, but looking at the code I found what i already tested : the @ldap_bind requiring cleartext password.

My issue may be similar to this unanswered question, but otherwise very few results associate LDAP and NTLM.


Here is the NTLM implementation that was on the server when I took on the project :

$headers = apache_request_headers();

if (@$_SERVER['HTTP_VIA'] != NULL) {
    //echo "Proxy bypass!";
}
elseif(!isset($headers['Authorization']) || $headers['Authorization'] == NULL) {
    header( "HTTP/1.0 401 Unauthorized" );
    header( "WWW-Authenticate: NTLM" );
    exit;
}

if(isset($headers['Authorization']) && substr($headers['Authorization'],0,5) == 'NTLM ') {
    $chaine    = substr($headers['Authorization'], 5);
    $chained64 = base64_decode($chaine);

    if(ord($chained64[8]) == 1) {
        $retAuth   = "NTLMSSP".chr(000).chr(002).chr(000).chr(000).chr(000).chr(000).chr(000).chr(000);
        $retAuth   .= chr(000).chr(040).chr(000).chr(000).chr(000).chr(001).chr(130).chr(000).chr(000);
        $retAuth   .= chr(000).chr(002).chr(002).chr(002).chr(000).chr(000).chr(000).chr(000).chr(000);
        $retAuth   .= chr(000).chr(000).chr(000).chr(000).chr(000).chr(000).chr(000);
        $retAuth64 = trim(base64_encode($retAuth));

        header( "HTTP/1.0 401 Unauthorized" );
        header( "WWW-Authenticate: NTLM $retAuth64" );
        exit;
    }   
    if(ord($chained64[8]) == 3) {
        //LM Response Security Buffer: 
        $lenght_LMRSB = (ord($chained64[15])*256 + ord($chained64[14]));
        $offset_LMRSB = (ord($chained64[17])*256 + ord($chained64[16]));
        $LMRSB = str_replace("\0","",substr($chained64, $offset_LMRSB, $lenght_LMRSB));
    
        //NTLM Response Security Buffer:
        $lenght_NTLMRSB = (ord($chained64[23])*256 + ord($chained64[22]));
        $offset_NTLMRSB = (ord($chained64[25])*256 + ord($chained64[24]));
        $NTLMRSB = str_replace("\0","",substr($chained64, $offset_NTLMRSB, $lenght_NTLMRSB));

        //Domain:
        $lenght_domain = (ord($chained64[31])*256 + ord($chained64[30]));
        $offset_domain = (ord($chained64[33])*256 + ord($chained64[32]));   
        $domain = str_replace("\0","",substr($chained64, $offset_domain, $lenght_domain));

        //Login:
        $lenght_login = (ord($chained64[39])*256 + ord($chained64[38]));
        $offset_login = (ord($chained64[41])*256 + ord($chained64[40]));
        $login = str_replace("\0","",substr($chained64, $offset_login, $lenght_login));

        echo "$LMRSB<br>";
        echo "$NTLMRSB<br>";

        //HERE i would like to verify credentials against my active directory before accepting the username as a real logged user
        $_SESSION["utilisateur"] = $login;
    }
}
1

There are 1 best solutions below

0
user1686 On

We choosed NTLM to authenticate windows users automatically, and that part seems to be working, I am retrieving an username and an answer to the NTLM challenge.

You cannot verify NTLM responses via LDAP bind.

I mean, you technically probably could relay the challenge and response to a SASL NTLM bind, but that's practically the same kind of "NTLM Relay Attack" that your security team usually wants to prevent from happening. If the necessary hardening options have been enabled, the NTLM response will be issued for a specific service SPN and will be rejected by the LDAP server. So even if it's possible in theory, it would be very unwise to rely on it continuing to be possible (even more so than using NTLM in the first place).

I read here one could transmit both the challenge and the user NTLM Response as a "NETLOGON_NETWORK_INFO", but I cant find any documentation on the way to do so.

Yes, but doing so requires an existing "Netlogon secure channel", and that requires a machine account.

The usual way of doing this is to install Samba (specifically the Winbind component) and literally join the web server to the domain (which is what establishes the Netlogon channel), at which point you'll be able to use the ntlm_auth CLI tool to validate NTLM challenges in the "official" way, by having Winbind relay them to the DC via the Netlogon protocol.

(You don't actually need to install the "user authentication" parts (pam_winbind/nsswitch) on your webserver, in case you're worried about users SSHing into the server; you only need the daemon.)

However, I would very strongly recommend against doing anything that involves NTLM. You have Active Directory, and Active Directory has had Kerberos since 2000. HTTP supports Kerberos authentication just as it does with NTLM, and you don't even need to roll it yourself – all you need is the mod_auth_gssapi Apache module (or the older mod_auth_kerb) and a keytab, and the web server will do all the work; you can just pick up the username from $_SERVER["REMOTE_USER"] in the PHP script.

(Validation of a Kerberos ticket doesn't even to contact the KDC, nor have any service running nor a special privileged channel; just a keytab containing the password for the webapp's service user account.)