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: PHP NTLM Github

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.

61 thoughts on “NTLM authentication in PHP – Now with NTLMv2 hash checking

  1. phillip

    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 Post author

    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. Cornelius Weiss

    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

    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 Post author

    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

    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. Loune Post author

    @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’]

  8. Dean

    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

  9. Luke

    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

  10. Florian Ledeboer

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

  11. Dave

    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?

  12. Loune Post author

    @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.

  13. Maxxx

    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.

  14. Endre

    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?

  15. Robert

    Hey Loune,
    You may want to take a look at this:
    http://adldap.sourceforge.net/

    I am using it in conjunction with your script to pull user’s groups after they authenticate. You should be able tot pull the user’s password they logged in with through firefox/ie as well (haven’t gotten that far so don’t know if this is already the case).

    You wont need samba in this instance to authenticate, just the adLDAP function call. Their example is a little wrong so if you’re interested in my input shoot me an e-mail or reply…

  16. Loune Post author

    @Robert, that’s an interesting usage. Thanks for sharing. If I’m understanding correctly, you’re not yet checking the password? To ensure security, it’s best to do that, although it’s difficult as you have to get the NTLMv2 hash. The reason I use samba is because you can get the hash. I’m been putting it off but I might finally post up one way to do it with a samba server.

  17. Robert

    I’m using adLDAP.php to check the password with the authentication server(in my case AD). So instead of passing the NTLM info from the browser to compare against a database/array, I’m passing it to adLDAP using one of its many functions available which returns true or false based on the authentications server’s response. At least that’s my goal.

    The adLDAP.php side is working perfectly for me now, I can pass it a username and pass and I’ll get back the groups that user is in (one of the many things adLDAP can do but happens to be what I need for my application). I now am at the point where I need to grab the user’s username through the browser passively (which IE and FF support) and stumbled upon your script here. With a little tweaking, your code should be able to pass the username/passwd to adLDAP instead of your hash array setup, which you would need to modify anyway to implement it with SAMBA.

    So my whole authent side is based on a logged in user opening a web browser which then sends un/pass info to my program, which uses adLDAP to verify the user is authenticated, upon true case the program uses adLDAP to retrieve the group names for that username as an array which I can then binary search to find the group I care about.

    Just seemed like an easier route to go than inventing something for SAMBA, and it’s the route I’m pursuing so I thought I’d share :-). The missing piece to your script as you know is verifying the username and password with an authentication server that is up-to-date on the user’s current password. adLDAP ahs already done the work in terms of talking to an LDAP server via PHP to do just that…

    Regards,
    Robert

  18. LaMi

    I set’up the NTLM handling with the client and it works very well. One problem: The username/password are not verified at the moment.

    The credentials are stored in the LDAP/AD and I see no way to directly fetch a hash to compare it with.

    So I want to try both mentioned ways to verify the credentials. But I was not successful yet with the provided information in this thread.

    @Robert: Am I right you call the authenticate() method of adLDAP with the username and one/both hashes you get via NTLM? can you give an example?

    @Loune: How do you use samba to verify the credentials provided via NTLM?

  19. Robert

    @LaMi I use the authenticate() method, but currently this method is expecting a plain text password which it then hashes before sending it off to the ad server. I’m currently trying to dissect the adLDAP code so I can pass the hash from Loune’s code directly into the authenticate function and not have adLDAP hash the hash, but my reverse engineering skills are extremely poor so I haven’t had luck in making Loune’s code give up the hash or plain text or making adLDAP accept an already hashed password. If I come up with anything besides using apache’s mod_auth_ntlm_winbind I’ll report back…

  20. Dave

    Hello Loune,

    This is a great piece of code. I am still having problems with IE8 prompting me for a username and password. IE7 works great and afetr the trusted URI changes in Firfox, that works great. Can you tell me which settings to change in IE8? Much appreciated.

  21. Loune Post author

    @Dave – Regarding the IE8 problem, I think in IE8, you need to add your PHP website as a trusted site. You can do this be going to Internet Options > Security tab, click on the Trusted sites icon, then the sites button. In the popup, add your website’s domain.

  22. Shadow

    Great work. Thanks for the help. What about logging a user off ? just calling ntlm_unset_auth(); doesn’t do the trick. The page reloads with the same message, you are logged in as X from Y/Z.

    Thank you in advance.

  23. Loune Post author

    @Shadow since it’s Single Sign On (SSO), you can’t really logout per se as if you log out, the first time you access the page, the browser automatically logs in again.

    If this was basic authentication, then showing a page with 403 status code should clear the authentication data in the browser.

  24. Shadow

    Come to think of it, you’re right :) Thank’s again for the effort of putting such a solid example together.

  25. peter

    Great example, thanks! One thing I wrestle with, though: is it possible to get the username on server-side when using NTLMv2? As it’s MD5 hashed, I’m a little bit uncertain…

  26. Loune Post author

    @peter Do you mean the password? The username is not hashed when sent by the browser.

  27. peter

    no, I’m looking for the username… I have a piece of code (jsp) that works with NTLMv1, but not with v2… I thought the difference is the hashing…. But if it’s not, what is? Thanks!

  28. Rois Cannon

    I got NTLM working in Linux/Apache but I also need the same credentials for MS SQL access. (This is on a private LAN.) Is it even possible to authenticate against AD and then use the same creds in PHP to authenticate MS SQL 2005 (which is also using the same AD) database access? I’d like to have SSO but I’ll have to pass if I can’t pass through the creds (or extract the password for access) on to authenticate SQL 2005.

    Thoughts?

  29. Shadow

    Great solution. Just for testing purposes, I have hash of my Windows password. How should I write the get_ntlm_user_hash function, to use this hash correctly ? Everything I’ve written isn’t doing the job. Thank you in advance.

  30. David C

    I must be missing something, because I simply cannot get this script to authenticate me using FireFox or IE :(
    My $get_ntlm_user_hash_callback is returning the password hash, but the ntlm_verify_hash function produces a different $blobhash to the $clientblobhash.
    I noticed that the $domain value is used in $blobhash, even though in an earlier comment you’ve said it shouldn’t have any effect. But every value I try hasn’t helped..
    Have spent all morning trying to figure it out, any traps I should be aware of? Thanks.

  31. Loune Post author

    @David It’s been a while since I looked at this, but as far as I remember the domain doesn’t matter because the server blob hash is generated with the domain provided by the client.

    What environment and what versions of software (php, firefox, ie, apache?) are you using in your test? I’ve just tried it with the ntlm.php script with apache 2 on firefox 9 / IE 9 and it works fine (prompt shows up and you enter the username/password in the userdb array).

  32. David C

    Thanks Loune for the quick reply.
    I’ve tried my Windows XP workstation: PHP 5.2.8 + IIS 5.1
    And a Linux Ubuntu server: PHP 5.3.1 + Apache 2.2.14
    With Firefox 8 and IE 8.
    Script ntlm.php 1.2 downloaded today.

    I am using the PHP framework CodeIgniter, but have tried testing outside the framework with the same problem. Popup comes up (because current Windows credentials didn’t work) and when I type in eg loune/test, the popup keeps coming up.
    I will try again another time, there’s probably something obvious that I’ll see after getting some sleep..

    FYI In Windows I got the following error, which I fixed by uncommenting the preceding line:
    Message: iconv() [function.iconv]: Detected an incomplete multibyte character in input string
    Filename: third_party/ntlm.php
    Line Number: 94

  33. Loune Post author

    @David I loaded up an XP machine (was using win7) and was able to replicate your issue. Seems like XP machines send NTLM (v1) by default where as the script expects NTLMv2. You need to force NTLMv2 as per this guide: http://www.imss.caltech.edu/node/396

    Not sure if there’s a way to force NTLMv2 from the server side, but I will amend the script to point that out if it gets a NTLM (v1) response from the client.

  34. David C

    @Loune Thanks a lot for checking this out. I’ve enabled NTLMv2 on my WinXP machine and everything is working now.

    Thanks for the great script!

  35. Sheyj

    Hello,
    Is there a way to close the ntlm messages between client and server after I get the user name and domain name?
    Because I found the post infomation from client side is empty in the user side.
    Thank you in advance.

  36. Sheyj

    I have fixed the problem.

    1: C –> S GET …

    2: C S GET …
    Authorization: NTLM

    4: C <– S 401 Unauthorized
    WWW-Authenticate: NTLM

    5: C –> S GET …
    Authorization: NTLM

    6: C <– S 200 Ok

    In the last message, server will send page content.
    change the http header to '401 Unauthorized' can close the NTLM message between the client and server.

  37. Hossain

    Hi,
    I’m working for a company with almost 5000 employees, all of whose username and pass are stored in central Windows AD and to write all these in the code or in a local DB is impractical.

    So, how can I use this code to check for the username and pass from the central AD instead of the local DB

    Please reply, I need this badly.

    Thanks
    M. Hossain

  38. Bhargav

    Nice article. I am totally new about this NTLM & LDAP.

    This will surely help me to complete my Client’s requirement. But addition to this i am also looking for the same what Hossain asked for in previous comment.

    —————————
    how can I use this code to check for the username and pass from the central AD instead of the local DB

    Your reply/help will really be appreciated.

    Thanks in advance!

  39. Unix

    I need to implement SSO for a tool to be launched for people of our organization. The tool should be able to detect which user is visiting our site. I am not sure how to implement that both at Apache and back end code side. I googled but nothing is given as constructive that would work 100%. Can anyone please guide me with some basics?

  40. Evert

    Hi!

    I’m trying to do some hacking with your library, to try to get Office to treat my server as a sharepoint endpoint.

    I can see the following requests happen:

    1. OPTIONS (returns empty NTLM header)
    2. OPTIONS with challenge (returns a NTLM response)

    After this ‘Document Connection’ stops making http requests and says ‘The connection failed’.

    Well I would love to hear if you have any pointers on how to proceed from this point :) Feel free to contact me on my email address. I’m the author of SabreDAV, which is a popular WebDAV server for PHP; and I’m in the mood for hacking sharepoint-ish support.

    Cheers,
    Evert

  41. Daum

    @Hossain – How did you get the password to use in the bind? I don’t see how to get the password via the protocol to pass to the bind?

    I have this working except I need to authenticate against AD/LDAP and am not sure how to do that part.

    Any one have any ideas on how to get that?

Leave a Reply

Your email address will not be published. Required fields are marked *