Writing an NTLMv2 SSO Server in Perl
Web browsers on Windows have the ability to authenticate seamlessly (SSO) when talking to a suitably configured web server. This is very convenient and thus a popular option in Windows based environments. The trouble starts when you want to implement this on a server that is not part of the respective Windows domain.
This seamless browser authentication is based on a proprietary protocol called NTLM. NTLM is a challenge response protocol which means that the password is never passed from the client to the server, rather the server asks the client a ‘question’ (challenge) which the client can only answer when it knows the correct password.
The original purpose of NTLM was to authenticate connections to Windows file shares. But Microsoft bolted the concept into its version of the HTTP protocol, true to its embrace and extend policy back in the day. This was all but straight forward since NTLM requires a persistent client-server connection for the authentication exchange while HTTP connections get closed once the client receives the servers response. Fortunately the HTTP/1.1 protocol had recently been introduced, improving performance by allowing for multiple requests to be handled over a single connection (keep alive). Microsoft used that feature to make its NTLM extension work.
Initiating NTLM authentication from the server side is pretty simple. All the server has to do, is to respond to a client request with:
HTTP/1.1 401 Unauthorized
this causes the client to retry the request, supplying an NTLM Type1 token:
GET / HTTP/1.1
Authorization: NTLM TlRMTVNTUAADAAAAGAAYAF4AA...
and this is where the trouble starts. In order for the server to engage into an NTLM authentication exchange with the browser, it has to have access to the authentication information Windows has stored in its password database. Since my web server is running on a lowly, unprivileged Linux box, there is no way it can get direct access to this kind of information. But there is a way around the problem, also know as Man In The Middle or Proxy approach:
- hand the Type1 token over to the Active Directory server and have it come up with the Type2 token
- pass the Type2 token on to the browser, which in turn answers with a Type3 token
- send the Type3 token to the Active Directory server completing the exchange
The Active Directory server speaks the LDAP protocol. This is pretty convenient as there is good support for LDAP in perl through the Net::LDAP module.
Authentication in LDAP works by sending a bind request together with some authentication information like username and password to the LDAP server. Nice and simple, except in this case it is not very helpful since we would like to authenticate using a multi step NTLM exchange. Fortunately LDAP is pretty flexible when it comes to authentication. The method we want is called GSS-SPNEGO. It is basically a wrapper for NTLM and Kerberos authentication mechanisms.
With SPNEGO we can wrap the Type1 token into an SPNEGO request and get a response from where a Type2 token can be extracted. The Type2 token is then handed back to the browser which responds with a Type3 token. The Type3 token gets wrapped into an SPNEGO structure again and is used to send a second bind request. This second request will now complete happily if suitable authentication information has been provided by the browser.
The result of the whole operation is that the web server has now an open and authenticated LDAP connection to the Active Directory server. This LDAP connection can be used to retrieve further information like for example the group memberships of the user.
Unfortunately the Net::LDAP module has no direct support for SPNEGO authentication, but it provides most of the infrastructure necessary for implementing it. After several days of experimentation and learning about ASN.1 and DER in the process I had successfully prototyped the whole system and saw my first successful authentication exchange happening. I have since cleaned up all the code and written two perl modules:
The Net::LDAP::SPNEGO module provides all the basic building blocks for implementing NTLM authentication in any perl based web frameworks that support keep alive client connections.
The Mojolicious::Plugin:SPNEGO module provides perfect integration for Mojolicious, making it exceedingly simple to implement NTLM authentication with forwarding to an Active Directory server:
Both modules come with a sample application ready for testing in your own environment.
Note that Windows will only use SSO for hosts which are in the ‘Local Internet’ so you may have to add your lowly Linux box into that list using Internet Options > Security > Local Internet > Sites > Advanced.