I am trying to implement NTLM authorisation for a web server written in Perl (or perhaps an XS module). My understanding is that it should work in the following way:
c -> s: GET
s -> c: 401, WWW-Authenticate: NTLM
c -> s: GET, Authorization: NTLM [Type1 Message]
s -> c: 401, WWW-Authenticate: NTLM [Type2 Message]
c -> s: GET, Authorization: NTLM [Type3 Message]
IF s.Check([Type3 Message]):
s -> c: 200
ELSE:
s -> c: 401
To generate the Type3 message I have used both Authen::Perl::NTLM and Authen::NTLM::HTTP, both of these seem to generate the messages perfectly fine, however, they offer no functionality to check the Type3 Message.
My next step has been to try and use Win32::IntAuth to authenticate the NTLM token. This is where I have been running in to trouble, the developer and other snippets of information found searching say that this module should be able to Authenticate an NTLM binary token.
The module wraps around some Win32 API calls, namely AcquireCredntialsHandle, AcceptSecurityContext, CompleteAuthToken and ImpersonateSecurityContext.
Unfortunately all my attempts to authenticate the NTLM token have failed at AcceptSecurityContext with either SEC_E_INVALID_TOKEN or SEC_E_INSUFFICIENT_MEMORY leading me to suggest my NTLM token isn't correct. Below is some snippets of code to help show my methods.
# other code
...
if (not defined $headers->header('Authorization')) {
initHandshake($response);
} else {
my $authHeader = $headers->header('Authorization');
if ($authHeader =~ m/^NTLM\s(.+)$/i) {
my $message = $1;
if (length($message) == 56) {
handleType1($response, $message);
} else {
handleType3($response, $message);
}
} else {
printf "ERROR - Unable to pull out an NTLM message.\n";
print $authHeader . "\n";
}
}
...
sub handleType3 {
my $response = shift();
my $message = shift();
print "handleType3 - ", $message, "\n";
my $auth = Win32::IntAuth->new(debug => 1);
my $token = $auth->get_token_upn(decode_base64($message)) or die
"Couldn'timpersonate user, ", $auth->last_err_txt();
print "Hurrargh. User ", $auth->get_username(), " authed!\n";
$response->status(200);
}
..
The full code can be viewed here: http://codepad.org/cpMWnFru
I managed to get this working by bastardizing Win32::IntAuth (which I believe has a bug in it). Essentially I wasn't holding the partial context created during the creation of the Type 2 token, this and the fact that there was an error in Win32::IntAuth:
my $buf_size = 4096;
my $sec_inbuf = pack("L L P$buf_size", $buf_size, SECBUFFER_TOKEN, $token);
This was causing a token error as it wasn't the correct length of the token, therefore:
my $sec_inbuf = pack("L L P" . length($token), length($token), SECBUFFER_TOKEN, $token);
Produced the correct results.
The previous code was changed to:
...
sub handleType1 {
my $response = shift();
my $message = shift();
print "handleType1 - |", ${$message}, "|\n";
my $challenge = acceptSecurityContext(${$message});
${$response}->status(401);
${$response}->header("WWW-Authenticate" => "NTLM " . $challenge);
}
...
sub handleType3 {
my $response = shift();
my $message = shift();
print "handleType3 - ", ${$message}, "\n";
if (acceptSecurityContext(${$message})) {
${$response}->status(200);
} else {
${$response}->status(401);
}
}
...
acceptSecurityContext is a function that follows this pseudoish code:
credentials = Win32->AcquireCredentialsHandle(...)
challenge = Win32->AcceptSecurityContext(credentials, token, globalCtx ? globalCtx : 0, ...)
Hopefully this helps people who might be in a similar boat. Feel free to contact me for full demo.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With