NTLM authentication in PHP – Now with NTLMv2 hash checking

A few years ago, I investigated NTLM and PHP and managed to write a simple PHP script that can retrieve the current windows username. However, it was only partly finished as it did not authenticate the user. Inspired by a recent comment, I’ve decided to revisit this problem and solve the authentication issue. For the better part of the afternoon, I wrestled with the NTLMv2 hash checking as detailed here.

It turns out that NT passwords are stored as a MD4 hash of the UTF16LE password string. So once we have this hash, we can begin verifying passwords. Obtaining this hash for a user is relatively easy with Samba, but seems to pose a challenge on Windows. In a later blog post I will detail how to integrate PHP authentication with Samba. For now, the code will assume you have already obtained a database of your users MD4 hashed passwords.

The crux of the NTLMv2 authentication involves using HMAC-MD5 on challenges and nonces using the MD4 hashed password as the key. The result is a 150 line source code that perform authentication on clients supporting NTLMv2. On the support NTLMv2, Internet Explorer supports it fine. Firefox on the other hand only has limited support for NTLMv2. In Firefox on Windows, if you have whitelisted your server with network.automatic-ntlm-auth.trusted-uris, Firefox will attempt to use Windows’ SSPI support (sys-ntlm) to perform single sign on. The SSPI module supports NTLMv2 fine. However, if you are using Firefox’s own cross platform NTLM module, you’re out of luck, it only supports the legacy NTLM and LM hashes. Perhaps it will support NTLMv2 in the future.

For Internet Explorer 8, intranet settings are now off by default, which means single sign on won’t automatically activate. To fix this, you should see a yellow bar prompting you whether to apply intranet settings.

The php ntlm authentication library is available here: ntlm.php.zip

To use it just put

include('ntlm.php');

function get_ntlm_user_hash($user) {
	$userdb = array('loune'=>'test', 'you'=>'gg', 'a'=> 'a');

	if (!isset($userdb[strtolower($user)]))
		return false;
	return mhash(MHASH_MD4, ntlm_utf8_to_utf16le($userdb[strtolower($user)]));
}

session_start();
$auth = ntlm_prompt("testwebsite", "testdomain", "mycomputer", "testdomain.local", "mycomputer.local", "get_ntlm_user_hash");

if ($auth['authenticated']) {
	print "You are authenticated as $auth[username] from $auth[domain]/$auth[workstation]";
}

You need to provide your own implementation of the callback function get_ntlm_user_hash($user) which should return the MD4/Unicode hashed password of the requested $user. You can get that by doing mhash(MHASH_MD4, ntlm_utf8_to_utf16le("password")). You also need session_start() as the script needs to persist challenge information across http requests.

Next time, I will blog about the best way to integrate it with Samba on Linux.

Tags: , ,

18 Responses to “NTLM authentication in PHP – Now with NTLMv2 hash checking”

  1. phillip says:

    Hi,
    could you describe the parameters in the ntlm_prompt function?
    What are “testwebsite”, “testdomain”, etc.?
    Furthermore, what should put in the $userdb hash?
    Last but not least, when do you plan to write your next article about how to integrate this script with Samba?
    Many thanks.
    Phillip

  2. Loune says:

    The first 5 arguments in ntlm_prompt, $targetname, $domain, $computer, $dnsdomain, $dnscomputer are just values which are injected in the NTLM messages sent to the browser. I think they are quite irrelevant in most cases as the browser seems to ignore them and pass on credentials regardless if they match the current domain. But just to be on the safe side, you should probably put your current windows domain as the values. If you’re not part of a windows domain, then just put any value for those.

    The $userdb in the example is a hard coded associative array of username as the key, and the plain text password as the value. You should rewrite this as a SQL query to lookup the password in a database.

    I’ll probably write up the samba integration post next week.

    cheers

  3. Hi Loune,

    thanks for sharing this! I needed a few moments to realize the different response types which are possible. To force the NTLMv2 response I needed to set:
    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\LSA\LMCompatibilityLevel
    to 3

  4. Steve says:

    Can you provide more information on what the arguements for ntlm_prompt need to be? I get the authentication window but it never works. I am sure it is because I am not sending the right arguements to the ntlm_prompt function.

    Thanks

  5. Loune says:

    Steve, I doubt it is the arguments that are causing the issue as the the first few string arguments are pretty much ignored by the browser. The only important bit is the callback function that returns the password. The PHP script only implements NTLMv2 hash authentication so make sure you’re using that (Firefox only supports v1, unless it’s SSO which uses windows’ SSPI).

  6. Dean says:

    I have complete control over my environment so can make any changes required.

    The long and short of it is that all i want to make is a page that says

    “welcome $USER”

    that displays the currently logged on windows user.

    I have used all your code examples and none work as I would like.

    what code would I need to merely display the name of the currently logged in user using Windows and IE or firefox?

    I will be extremely grateful if you can tell me.

    Thanks
    Dean

  7. Dean says:

    I am using IIS but am more than happy to move to APACHE if that makes life easier.

  8. Loune says:

    @Dean – the guide was made for Apache. If you’re using IIS, it should be even easier. After enabling AD authentication for the website, you can just get the username in $_SERVER['AUTH_USER']

  9. Dean says:

    Thank you so much! i will give that a shot, if it works ill click some ads on the site :D

    Dean

  10. Dean says:

    This works for me! It isn’t automated but because of your help I have managed to refine the work i was doing and hopefully i can automate it later!

    Thanks so much.

    Dean

  11. Luke says:

    Dean, can you share your code example please?
    Also, I am trying to get this working with ISA server doing FBA and passing NTLM to the backend web server which is IIS. Any help appreciated

  12. Florian Ledeboer says:

    Is there probably a way to use the ntlm password hash to authenticate the user against an AD with ldap_bind() or something else?

  13. Simon says:

    What about https with this version?

  14. Dave says:

    NICE Writeup!!!

    I am using an NTLM authenticated IIS server and would like to pass through the NTLM authentication to another web-services server (same domain, same authentication credentials). I can get the user through $_SERVER['AUTH_USER'], but, obviously there’s no server Var for the password. I don’t have access to the password repository either.

    Can I take the NTLM auth. values from my request header and just pass them through to the header for the downstream web services call?

  15. Loune says:

    @Simon https should work fine with this NTLM script

    @Dave what you’re asking is really to add client NTLM authentication to your SOAP client. This article is about NTLM on the server side. However, you might be able to pass through the NTLM from the web service end point. NTLM is a three way handshake so you probably have to modify the SOAP client to save state in the session as each step of the handshake you need to return to the browser. You have to make sure you turn off IIS NTLM and take the exact header from the SOAP and pass it back to the browser. Lastly this will only work for a single service call per PHP request.

    A better way would probably to rewrite the SOAP client to support proper NTLM.

  16. Maxxx says:

    Hi !

    I used an older version of your script but now i can’t use the apache_request_headers() function with the configuration of my web hosting (php in CGI mod).

    Do you have a solution for my trouble ?

    Thx a lot and sorry for my english.

  17. Loune says:

    @Maxxx You might able to use $_SERVER['HTTP_AUTHORIZATION'] instead

  18. Endre says:

    Hi Luone,

    Thanks for your nice article, but I can not find the next part with Samba integration. :)
    Can you help me to find it?

Leave a Reply