Wednesday, March 07, 2012

NTLMv2 Client-side

In my last post I had successfully used the Http "Negotiate" authentication mechanism to send a Kerberos ticket, which allowed me to login to the domain as my user to access a protected resource. However, to do that I had to enable the AllowTgtSessionKey registry entry in Windows 7, which weakened the security posture.

I fiddled with the JRE's implementation of NtlmV2, which we have to use since our lmcompatibility level will be 4, but could not get it to work for some reason. I tried tracking that down but gave up. The only other solution was to go native. For this, there is a package called JNA.

There is a some coverage on the internet of server-based NTLMv2 using JNA (Waffle), but it didn't touch much on client connections. I tried modifying some of Waffle's code to do what I wanted, but it seemed like the variables were all set up to use Negotiate, and it wasn't really getting me anywhere.

I realize this isn't the prettiest code, but it's what I finally came up with, and works for authenticating via NTLMv2 with default credentials (SSO, no username/password). I achieved this by investigating more of what the JRE was doing in NTLMAuthSequence.c as well as another project (JavaSVN), both of which I used to reverse engineer the below client solution.

This does not maintain a connection, it simply performs a one-time handshake, sending all 3 NTLM message types for NTLMv2, and retrieves a cookie value used in later web service calls...


private HttpCookie doWin7NTLM() throws IOException, GSSException, EncryptorException, NTLMEngineException {
String securityPackage = "NTLM";
// client credentials handle
DefaultHttpClient httpclient;
Sspi.CredHandle cred = new Sspi.CredHandle();
Sspi.TimeStamp ltime = new Sspi.TimeStamp();
Sspi.CtxtHandle ctx = new Sspi.CtxtHandle();

// Grab the initial default client credentials handle
int rc = Secur32.INSTANCE.AcquireCredentialsHandle(null,
securityPackage, new NativeLong(Sspi.SECPKG_CRED_OUTBOUND), null,
Pointer.NULL, null, null,
cred, ltime);
if (rc != W32Errors.SEC_I_CONTINUE_NEEDED && rc != W32Errors.SEC_E_OK){
throw new Win32Exception(rc);
}

Sspi.SecBufferDesc outBuffDesc = new Sspi.SecBufferDesc();
Sspi.SecBuffer.ByReference outBuff = new Sspi.SecBuffer.ByReference();
outBuff.cbBuffer = new NativeLong(512);
outBuff.BufferType = new NativeLong(Sspi.SECBUFFER_TOKEN);
outBuff.pvBuffer = new Memory(512);
outBuff.write();
outBuffDesc.ulVersion = new NativeLong(0);
outBuffDesc.cBuffers = new NativeLong(1);
outBuffDesc.pBuffers = (Sspi.SecBuffer.ByReference[])outBuff.toArray(1);
outBuffDesc.write();
NativeLongByReference attr = new NativeLongByReference();

// Use the default credentials handle to generate an NTLMv2 Type 1 Message (just workstation/domain name)
rc = Secur32.INSTANCE.InitializeSecurityContext(cred, null,
null, new NativeLong(0), new NativeLong(0),
new NativeLong(Sspi.SECURITY_NATIVE_DREP), null, new NativeLong(0), ctx, outBuffDesc,
attr, null);
if (rc != W32Errors.SEC_I_CONTINUE_NEEDED && rc != W32Errors.SEC_E_OK){
throw new Win32Exception(rc);
}

// Read buffer containing NTLM Type 1 message data
byte[] result = null;
outBuffDesc.read();
outBuff.read();
if (outBuff.cbBuffer.intValue() > 0) {
result = outBuff.pvBuffer.getByteArray(0, outBuff.cbBuffer.intValue());
}

freeWin7Mem(null, null, outBuffDesc);

//Send NTLM type 1 message to server
httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet request = new HttpGet(clientXmlProperties.getProperty(IWA_SERVER_URL));
request.setHeader("Connection", "keep-alive");
request.setHeader("Authorization", securityPackage + " " + new String(Base64.encodeBase64(result)));
HttpResponse response = httpclient.execute(request, localContext);

//Receive the NTLM type 2 message from the server
String continueToken = response.getFirstHeader("WWW-Authenticate").getValue().substring(securityPackage.length() + 1);
if (continueToken != null)
{
byte[] continueTokenBytes = Base64.decodeBase64(continueToken.getBytes());

Sspi.SecBuffer.ByReference inBuff = new Sspi.SecBuffer.ByReference();
inBuff.cbBuffer = new NativeLong(continueTokenBytes.length);
inBuff.BufferType = new NativeLong(Sspi.SECBUFFER_TOKEN);
inBuff.pvBuffer = new Memory(continueTokenBytes.length);
inBuff.pvBuffer.write(0, continueTokenBytes, 0, continueTokenBytes.length);
inBuff.write();
Sspi.SecBufferDesc inBuffDesc = new Sspi.SecBufferDesc();
inBuffDesc.ulVersion = new NativeLong(0);
inBuffDesc.cBuffers = new NativeLong(1);
inBuffDesc.pBuffers = (Sspi.SecBuffer.ByReference[])inBuff.toArray(1);
inBuffDesc.write();
outBuffDesc = new Sspi.SecBufferDesc();
outBuff = new Sspi.SecBuffer.ByReference();
outBuff.cbBuffer = new NativeLong(1024);
outBuff.BufferType = new NativeLong(Sspi.SECBUFFER_TOKEN);
outBuff.pvBuffer = new Memory(1024);
outBuff.write();
outBuffDesc.ulVersion = new NativeLong(0);
outBuffDesc.cBuffers = new NativeLong(1);
outBuffDesc.pBuffers = (Sspi.SecBuffer.ByReference[])outBuff.toArray(1);
outBuffDesc.write();
attr = new NativeLongByReference();

//Use the received type 2 message to generate the type 3 message which hopefully the server will accept
rc = Secur32.INSTANCE.InitializeSecurityContext(cred, ctx.isNull() ? null : ctx,
null, new NativeLong(0), new NativeLong(0),
new NativeLong(Sspi.SECURITY_NATIVE_DREP), inBuffDesc, new NativeLong(0), ctx, outBuffDesc,
attr, null);
if (rc != W32Errors.SEC_I_CONTINUE_NEEDED && rc != W32Errors.SEC_E_OK){
throw new Win32Exception(rc);
}

//Grab the data from the native call (i.e. the NTLM type 3 message)
byte[] finalResult = null;
outBuffDesc.read();
outBuff.read();
if (outBuff.cbBuffer.intValue() > 0) {
finalResult = outBuff.pvBuffer.getByteArray(0, outBuff.cbBuffer.intValue());
}
freeWin7Mem(null, null, inBuffDesc);
freeWin7Mem(cred, ctx, outBuffDesc);
//Prepare the outgoing NTLM type 3 message in the request header
request.setHeader("Authorization", securityPackage + " " + new String(Base64.encodeBase64(finalResult)));

//Clear data out of httpclient so we can re-use the connection
HttpEntity fakeEntity = response.getEntity();
if (fakeEntity != null) {
InputStream instream = fakeEntity.getContent();
BufferedReader reader = new BufferedReader(
new InputStreamReader(instream));
// do something useful with the response
System.out.println(reader.readLine());
instream.close();
}

//Send the NTLM type 3 message
httpclient.execute(request, localContext);
}
return getCookie(httpclient);
}

private void freeWin7Mem(Sspi.CredHandle credHand, Sspi.CtxtHandle ctxHandle, Sspi.SecBufferDesc outBuff) {
if (null != credHand) {
Secur32.INSTANCE.FreeCredentialsHandle(credHand);
credHand = null;
}

Secur32.INSTANCE.FreeContextBuffer(outBuff.getPointer());

if (null != ctxHandle) {
Secur32.INSTANCE.DeleteSecurityContext(ctxHandle);
ctxHandle = null;
}
}

private HttpCookie getCookie(DefaultHttpClient httpClient) throws IOException {
HttpCookie wamCookie = null;
List cookies = httpClient.getCookieStore().getCookies();
for (Cookie cookie : cookies)
{
if (cookie.getName().equalsIgnoreCase(SECURE_COOKIE_NAME))
{
wamCookie = new HttpCookie(cookie.getName(), cookie.getValue());
wamCookie.setMaxAge(CLIENT_EXPIRATION_TIME);
wamCookie.setComment(String.valueOf(new Date()));
}
}
return wamCookie;
}

Tuesday, February 28, 2012

Windows 7 Negotiate/Kerberos

The company I work for had been having issues getting NTLM authentication to work between a Windows 7 client PC, domain servers, and CXF web services. In the end I got it working through HTTP Negotiate Auth and a registry setting, below is the paragraph I sent as an explanation:


With Windows 7 and changing the lmcompatibility level to 4 on the client (Clients use NTLMV2 for auth and NTLMv2 session security if server accepts, Domain controller refuses LM auth responses but accepts NTLM and NTLMv2 -- http://technet.microsoft.com/en-us/library/cc960646.aspx), the client and server were no longer accepting LM authentication. Kerberos is preferred to NTLM, and since when LM was disabled the servers responded with HTTP 401 errors indicating that the only allowable mechanisms were NTLM and Negotiate (NTLM or Kerberos -- http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-auth.html), it was decided to use Negotiate with Kerberos, since that is what the below Linux code was already doing. On windows this required an extra registry setting "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters\AllowTgtSessionKey", a REG_DWORD set to 1 -- http://support.microsoft.com/kb/837361, along with the regular Kerberos config files (krb5.conf and login.conf).

Saturday, July 09, 2011

Wow

it's been a little while since I've posted. Lots of things change, still loving life and doing it in Illinois. This year has been flying by, and I'm about to turn 31. I have a wonderful family and 4 beautiful nieces (about to have another niece/nephew, holidays are getting crowded :)).

Just bought a 2005 Honda Repsol CBR 1000RR. It's a lot of fun. I think mostly I put what I'd put here on facebook posts now. Do people still blog?

Wednesday, September 30, 2009

Today's Interesting News

Someone very hungry.

Somebody with a weird fetish.

Somebody who is avoiding being hungry, and winning himself a 14" TV set.

Well there you go.