public
Last active

Very simple NTLM authenticator

  • Download Gist
main.coffee
CoffeeScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
http = require 'http'
ntlm = require './ntlm'
 
serverUrl = 'localhost:30000/login'
server = http.createServer (req, res) ->
ntlm.handleRequest req, serverUrl, (err, response, httpCode) ->
if err?
console.log 'ERROR:', err
res.writeHead 400
res.end err
else
console.log 'response', response, 'httpCode', httpCode
headers =
'WWW-Authenticate': response
res.writeHead httpCode, headers
res.end response
 
server.listen 20001
ntlm_parser.coffee
CoffeeScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
binary = require 'binary'
crypto = require 'crypto'
restler = require 'restler'
 
class Flags
@NEGOTIATE_UNICODE = 0x01
@NEGOTIATE_OEM = 0x02
@REQUEST_TARGET = 0x04
@NEGOTIATE_NTLM = 0x200
@NEGOTIATE_DOMAIN_SUPPLIED = 0x1000
@NEGOTIATE_WORKSTATION_SUPPLIED = 0x2000
@NEGOTIATE_LOCAL_CALL = 0x4000
@NEGOTIATE_ALWAYS_SIGN = 0x8000
@NEGOTIATE_TARGET_IS_DOMAIN = 0x10000
@NEGOTIATE_TARGET_IS_SERVER = 0x20000
@NEGOTIATE_TARGET_IS_SHARE = 0x40000
@NEGOTIATE_NTLM2_KEY = 0x80000
@NEGOTIATE_TARGET_SUPPLIED = 0x800000
@NEGOTIATE_128 = 0x20000000
@NEGOTIATE_56 = 0x80000000
 
class GenerateNtlm
constructor: (type) ->
@buffer = new Buffer 1024
@bufferOffset = 0
@writeHeader()
@writeType type
 
write: (data, length) ->
for i in [0...length]
@buffer.writeUInt8 data[i], @bufferOffset
@bufferOffset += 1
 
writeUInt16: (v) ->
@buffer.writeUInt16LE v, @bufferOffset
@bufferOffset += 2
 
writeUInt32: (v) ->
@buffer.writeUInt32LE v, @bufferOffset
@bufferOffset += 4
 
writeStaticString: (s) ->
@buffer.write s, @bufferOffset
@bufferOffset += s.length
 
@buffer.writeUInt8 0, @bufferOffset
@bufferOffset += 1
 
writeType: (type) ->
@writeUInt32 type
 
writeHeader: ->
@writeStaticString "NTLMSSP"
 
writeFlags: (flags) ->
@writeUInt32 flags
 
writeString: (str) ->
@writeUInt16 str.length
@writeUInt16 str.length
@writeUInt32 0 # offset
 
finalize: ->
buf = @buffer.slice 0, @bufferOffset
"NTLM " + buf.toString 'base64'
 
class GenerateNtlm1 extends GenerateNtlm
constructor: () ->
super 1
@writeFlags Flags.NEGOTIATE_UNICODE | Flags.NEGOTIATE_OEM
 
class GenerateNtlm2 extends GenerateNtlm
constructor: ->
super 2
 
generate: (targetName, done) ->
@writeString targetName
@writeFlags Flags.NEGOTIATE_UNICODE | Flags.NEGOTIATE_OEM
crypto.randomBytes 8, (ex, buf) =>
@nonce = buf
@write @nonce, 8
result = @finalize()
done null, result
 
 
class ParseNtlm
parseLength: ->
@parser.word16lu('length').vars.length
 
parseUint32: ->
@parser.word32lu('temp').vars.temp
 
parseLengthAndOffset: ->
result =
length: @parseLength()
allocated: @parseLength()
offset: @parser.word32lu('offset').vars.offset
result
parseString: ->
data = @parseLengthAndOffset()
return '' if data.length is 0
start = data.offset
end = data.offset + data.length - 1
stringBuffer = @buffer.slice start, end
parsedString = stringBuffer.toString 'utf-8'
parsedString
parseStaticString: (len) ->
buf = @parser.buffer('temp', len).vars.temp
console.log 'buf slice len:', buf.length
buf.toString().slice 0, -1
 
 
class ParseNtlmHeader extends ParseNtlm
parse: (authentication, done) ->
@buffer = new Buffer authentication, 'base64'
@parser = binary.parse @buffer
protocol = @parseStaticString 8
if protocol isnt 'NTLMSSP'
done "Wrong header: #{protocol}"
return
type = @parseUint32()
if type < 1 or type > 3
done "Unknown type: #{type}"
return
done null, type
 
class ParseNtlm3 extends ParseNtlm
constructor: (authentication) ->
@buffer = new Buffer authentication, 'base64'
@parser = binary.parse @buffer
@protocol = @parseStaticString 8
type = @parser.word8('type').vars.type
@parser.word8 ''
@parser.word8 ''
@parser.word8 ''
@lanManagerResponse = @parseString()
@ntResponse = @parseString()
@domain = @parseString()
@username = @parseString()
@workstation = @parseString()
debugOutput: ->
console.log "Username: '#{@username}'"
console.log "domain: '#{@domain}'"
console.log "workstation: '#{@workstation}'"
 
 
respond = (authorization, loginServerUrl, done) ->
if authorization?
isNtlm = authorization.substr(0, 4) is 'NTLM'
authorization = authorization.substr(5)
headerParse = new ParseNtlmHeader
headerParse.parse authorization, (err, type) ->
if err?
done err
return
switch type
when 3
console.log 'got packet 3'
parser = new ParseNtlm3 authorization
parser.debugOutput()
loginOptions =
username: parser.username
operation = restler.postJson loginServerUrl, loginOptions
operation.on 'complete', (data, res) ->
console.log 'data:', data
done null, data, 200
return
when 1
console.log 'got packet 1'
packet = new GenerateNtlm2
packet.generate '', (err, response) ->
authenticateResponse = response
console.log authenticateResponse
done null, authenticateResponse, 401
return
else
done "Unexpected type:#{type}"
return
else
authenticateResponse = 'NTLM'
done null, authenticateResponse, 401
 
exports.handleRequest = (req, loginServerUrl, done) ->
authorization = req.headers['authorization']
console.log 'authorization', authorization
respond authorization, loginServerUrl, done
 
generateSessionKey = (done) ->
crypto.randomBytes 32, (ex, buf) =>
done null, buf.toString('hex')

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.