NTLM Username Decode for logging (11/24/2010)
This user-contributed article describes how to parse and decode credentials in NTLM authentication. You can then log these credentials for audit reasons.
It is a requirement that we log all usernames against incoming requests, so that should there be a case of misuse, we will know which user generated the request, and which workstation was used. I have included Domain, purely for if your site has more than one domain that users could authenticate to.
NTLM background
NTLM uses three different NTLM message types to complete a handshake for a given request. These are:
- NTLM Type-1 Message: This contains the hostname, the domain name, and the fact that it is a NTLM request type1, to initiate the correct stage in the handshake.
- NTLM Type-2 Message: This contains a NTLM challenge from the server.
- NTLM Type-3 Message: This contains DOMAIN, USERNAME, and Workstation\Hostname.
For further information on the NTLM handshake go here: http://davenport.sourceforge.net/ntlm.html
The Plan
To fulfill the requirements and to ease load on the servers we will ignore Type-1/Type-2 messages, and instead just decode/process Type-3 Messages.
We want to make the data available to the traffic manager to log, but still keep the request secure, i.e. not transmit the decoded information in headers. We will use the connection.data.set($key, $value) function; to access the values, use the %{key}d macro in the Virtual Server -> Edit -> Request Logging -> log!format parameter.
The Code
Please note Basic is included as a just in case you need it:
- $h = http.getHeader( "Authorization" );
-
- # Although none of our websites use this,i have still included Basic Authenticaton.
- if( string.startsWith( $h, "Basic " ) ) {
- $enc = string.skip( $h, 6 );
- $userpasswd = string.base64decode( $enc );
- log.info( "Basic: User used: ".$userpasswd ); }
-
- # Test to see if the Authorization token, begins with NTLM
- if(string.startsWith( $h, "NTLM " )) {
-
- # Skip Authorization Token Header 'NTLM ', so we can decode just token.
- $enc = string.skip( $h, 5 );
- $ntlmpacket= string.base64decode( $enc ); #Decode TOKEN
-
- # Username, DOMAIN, and Workstation are only in the third NTLM handshake.
- # So we ignore the initial handshakes and test for the third, '3'.
- if((string.bytesToInt(string.substring($ntlmpacket, 8, 8))) == 3) {
- # Extract header fields.
- $B = string.substring($ntlmpacket, 28, 51);
-
- # Select and decode the Domain Offset, Domain length, Username length
- # and Workstation length.
- $dLen = ((string.bytesToInt(string.substring($B, 0, 0)))
- +((string.bytesToInt(string.substring($B, 1, 1)))*256));
- $dOff = ((string.bytesToInt(string.substring($B, 4, 4)))
- +((string.bytesToInt(string.substring($B, 5, 5)))*256)
- +((string.bytesToInt(string.substring($B, 6, 6)))*(256*256))
- +((string.bytesToInt(string.substring($B, 7, 7)))*(256*256*256)));
- $uLen = ((string.bytesToInt(string.substring($B, 8, 8)))
- +((string.bytesToInt(string.substring($B, 9, 9)))*256));
- $wLen = ((string.bytesToInt(string.substring($B, 16, 16)))
- +((string.bytesToInt(string.substring($B, 17, 17)))*256));
-
- # The data we are after is back to back, so we only need the initial offset;
- # we can decode the rest with the lengths of the previous key.
- connection.data.set("NTLM_Domain",
- string.substring($ntlmpacket, $dOff, $dOff + $dLen - 1));
- connection.data.set("NTLM_User",
- string.substring($ntlmpacket, $dOff + $dLen, $dOff + $dLen + $uLen - 1));
- connection.data.set("NTLM_Workstation",
- string.substring($ntlmpacket, $dOff + $dLen + $uLen,
- $dOff + $dLen + $uLen + $wLen - 1));
- }
- }