Module: Net::NTLM

Defined in:
lib/net/ntlm.rb,
lib/net/ntlm/md4.rb,
lib/net/ntlm/rc4.rb,
lib/net/ntlm/blob.rb,
lib/net/ntlm/field.rb,
lib/net/ntlm/client.rb,
lib/net/ntlm/string.rb,
lib/net/ntlm/message.rb,
lib/net/ntlm/version.rb,
lib/net/ntlm/int16_le.rb,
lib/net/ntlm/int32_le.rb,
lib/net/ntlm/int64_le.rb,
lib/net/ntlm/field_set.rb,
lib/net/ntlm/exceptions.rb,
lib/net/ntlm/encode_util.rb,
lib/net/ntlm/target_info.rb,
lib/net/ntlm/message/type0.rb,
lib/net/ntlm/message/type1.rb,
lib/net/ntlm/message/type2.rb,
lib/net/ntlm/message/type3.rb,
lib/net/ntlm/client/session.rb,
lib/net/ntlm/channel_binding.rb,
lib/net/ntlm/security_buffer.rb

Defined Under Namespace

Modules: VERSION Classes: Blob, ChannelBinding, Client, EncodeUtil, Field, FieldSet, Int16LE, Int32LE, Int64LE, InvalidTargetDataError, Md4, Message, NtlmError, SecurityBuffer, String, TargetInfo

Constant Summary collapse

LM_MAGIC =
"KGS!@\#$%"
TIME_OFFSET =
11644473600
MAX64 =
0xffffffffffffffff
LAN_MANAGER_HEX_DIGEST_REGEXP =

Valid format for LAN Manager hex digest portion: 32 hexadecimal characters.

/[0-9a-f]{32}/i
NT_LAN_MANAGER_HEX_DIGEST_REGEXP =

Valid format for NT LAN Manager hex digest portion: 32 hexadecimal characters.

/[0-9a-f]{32}/i
DATA_REGEXP =

Valid format for an NTLM hash composed of ‘’<LAN Manager hex digest>:<NT LAN Manager hex digest>‘`.

/\A#{LAN_MANAGER_HEX_DIGEST_REGEXP}:#{NT_LAN_MANAGER_HEX_DIGEST_REGEXP}\z/
BLOB_SIGN =
0x00000101
SSP_SIGN =
"NTLMSSP\0"
FLAGS =
{
    :UNICODE              => 0x00000001,
    :OEM                  => 0x00000002,
    :REQUEST_TARGET       => 0x00000004,
    :SIGN                 => 0x00000010,
    :SEAL                 => 0x00000020,
    :NEG_DATAGRAM         => 0x00000040,
    :NEG_LM_KEY           => 0x00000080,
    :NTLM                 => 0x00000200,
    :NEG_ANONYMOUS        => 0x00000800,
    :DOMAIN_SUPPLIED      => 0x00001000,
    :WORKSTATION_SUPPLIED => 0x00002000,
    :ALWAYS_SIGN          => 0x00008000,
    :TARGET_TYPE_DOMAIN   => 0x00010000,
    :TARGET_TYPE_SERVER   => 0x00020000,
    :NTLM2_KEY            => 0x00080000,
    :NEG_IDENTIFY         => 0x00100000,
    :NON_NT_SESSION_KEY   => 0x00400000,
    :TARGET_INFO          => 0x00800000,
    :NEG_VERSION          => 0x02000000,
    :KEY128               => 0x20000000,
    :KEY_EXCHANGE         => 0x40000000,
    :KEY56                => 0x80000000,
    # Undocumented flags:
    :MBZ9                 => 0x00000008,
    :NETWARE              => 0x00000100,
    :NEG_NT_ONLY          => 0x00000400,
    :MBZ7                 => 0x00000800, # alias for :NEG_ANONYMOUS
    :LOCAL_CALL           => 0x00004000,
}.freeze
FLAG_KEYS =
FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
DEFAULT_FLAGS =
{
    :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
    :TYPE2 => FLAGS[:UNICODE],
    :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
}

Class Method Summary collapse

Class Method Details

.apply_des(plain, keys) ⇒ Object



128
129
130
131
132
133
134
135
136
137
# File 'lib/net/ntlm.rb', line 128

def apply_des(plain, keys)
  keys.map {|k|
    # Spec requires des-cbc, but openssl 3 does not support single des
    # by default, so just do triple DES (EDE) with the same key
    dec = OpenSSL::Cipher.new("des-ede-cbc").encrypt
    dec.padding = 0
    dec.key = k + k
    dec.update(plain) + dec.final
  }
end

.gen_keys(str) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Each byte of a DES key contains seven bits of key material and one odd-parity bit. The parity bit should be set so that there are an odd number of 1 bits in each byte.

Parameters:

  • str (String)

    String to generate keys for



120
121
122
123
124
125
126
# File 'lib/net/ntlm.rb', line 120

def gen_keys(str)
  split7(str).map{ |str7|
    bits = split7(str7.unpack("B*")[0]).inject('')\
      {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
    [bits].pack("B*")
  }
end

.is_ntlm_hash?(data) ⇒ Boolean

Takes a string and determines whether it is a valid NTLM Hash

Parameters:

  • the (String)

    string to validate

Returns:

  • (Boolean)

    whether or not the string is a valid NTLM hash



89
90
91
92
93
94
95
96
97
# File 'lib/net/ntlm.rb', line 89

def is_ntlm_hash?(data)
  decoded_data = data.dup
  decoded_data = EncodeUtil.decode_utf16le(decoded_data)
  if DATA_REGEXP.match(decoded_data)
    true
  else
    false
  end
end

.lm_hash(password) ⇒ Object

Generates a LAN Manager Hash

Parameters:

  • password (String)

    The password to base the hash on



141
142
143
144
# File 'lib/net/ntlm.rb', line 141

def lm_hash(password)
  keys = gen_keys password.upcase.ljust(14, "\0")
  apply_des(LM_MAGIC, keys).join
end

.lm_response(arg) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
# File 'lib/net/ntlm.rb', line 187

def lm_response(arg)
  begin
    hash = arg[:lm_hash]
    chal = arg[:challenge]
  rescue
    raise ArgumentError
  end
  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
  keys = gen_keys hash.ljust(21, "\0")
  apply_des(chal, keys).join
end

.lmv2_response(arg, opt = {}) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/net/ntlm.rb', line 242

def lmv2_response(arg, opt = {})
  key = arg[:ntlmv2_hash]
  chal = arg[:challenge]

  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)

  if opt[:client_challenge]
    cc  = opt[:client_challenge]
  else
    cc = rand(MAX64)
  end
  cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)

  OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
end

.ntlm2_session(arg, opt = {}) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/net/ntlm.rb', line 258

def ntlm2_session(arg, opt = {})
  begin
    passwd_hash = arg[:ntlm_hash]
    chal = arg[:challenge]
  rescue
    raise ArgumentError
  end
  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)

  if opt[:client_challenge]
    cc = opt[:client_challenge]
  else
    cc = rand(MAX64)
  end
  cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)

  keys = gen_keys(passwd_hash.ljust(21, "\0"))
  session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
  response = apply_des(session_hash, keys).join
  [cc.ljust(24, "\0"), response]
end

.ntlm_hash(password, opt = {}) ⇒ Object

Generate an NTLM Hash

Parameters:

  • password (String)

    The password to base the hash on

  • opt (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opt):

  • :unicode (Object) — default: false

    Unicode encode the password



149
150
151
152
153
154
155
# File 'lib/net/ntlm.rb', line 149

def ntlm_hash(password, opt = {})
  pwd = password.dup
  unless opt[:unicode]
    pwd = EncodeUtil.encode_utf16le(pwd)
  end
  Net::NTLM::Md4.digest pwd
end

.ntlm_response(arg) ⇒ Object



199
200
201
202
203
204
205
# File 'lib/net/ntlm.rb', line 199

def ntlm_response(arg)
  hash = arg[:ntlm_hash]
  chal = arg[:challenge]
  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
  keys = gen_keys hash.ljust(21, "\0")
  apply_des(chal, keys).join
end

.ntlmv2_hash(user, password, target, opt = {}) ⇒ Object

Generate a NTLMv2 Hash

Parameters:

  • user (String)

    The username

  • password (String)

    The password

  • target (String)

    The domain or workstation to authenticate to

  • [Boolean] (Hash)

    a customizable set of options



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/net/ntlm.rb', line 162

def ntlmv2_hash(user, password, target, opt={})
  if is_ntlm_hash? password
    decoded_password = EncodeUtil.decode_utf16le(password)
    ntlmhash = [decoded_password.upcase[33,65]].pack('H32')
  else
    ntlmhash = ntlm_hash(password, opt)
  end

  if opt[:unicode]
    # Uppercase operation on username containing non-ASCI characters
    # after behing unicode encoded with `EncodeUtil.encode_utf16le`
    # doesn't play well. Upcase should be done before encoding.
    user_upcase = EncodeUtil.decode_utf16le(user).upcase
    user_upcase = EncodeUtil.encode_utf16le(user_upcase)
  else
    user_upcase = user.upcase
  end
  userdomain = user_upcase + target

  unless opt[:unicode]
    userdomain = EncodeUtil.encode_utf16le(userdomain)
  end
  OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
end

.ntlmv2_response(arg, opt = {}) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/net/ntlm.rb', line 207

def ntlmv2_response(arg, opt = {})
  begin
    key = arg[:ntlmv2_hash]
    chal = arg[:challenge]
    ti = arg[:target_info]
  rescue
    raise ArgumentError
  end
  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)

  if opt[:client_challenge]
    cc  = opt[:client_challenge]
  else
    cc = rand(MAX64)
  end
  cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)

  if opt[:timestamp]
    ts = opt[:timestamp]
  else
    ts = Time.now.to_i
  end
  # epoch -> milsec from Jan 1, 1601
  ts = 10_000_000 * (ts + TIME_OFFSET)

  blob = Blob.new
  blob.timestamp = ts
  blob.challenge = cc
  blob.target_info = ti

  bb = blob.serialize

  OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
end

.pack_int64le(val) ⇒ Object

Convert the value to a 64-bit little-endian integer

Parameters:

  • val (String)

    The string to convert



101
102
103
# File 'lib/net/ntlm.rb', line 101

def pack_int64le(val)
  [val & 0x00000000ffffffff, val >> 32].pack("V2")
end

.split7(str) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds an array of strings that are 7 characters long

Parameters:

  • str (String)

    The string to split



108
109
110
111
112
113
114
# File 'lib/net/ntlm.rb', line 108

def split7(str)
  s = str.dup
  until s.empty?
    (ret ||= []).push s.slice!(0, 7)
  end
  ret
end