See #2002. Adds a standard, native NTLM library for Ruby that lends itself very well to HTTP authentication. (Ruby licensed by yrock and Minero Aoki)
Usage: See lib/net/ntlm.rb.ut.rb git-svn-id: file:///home/svn/framework3/trunk@9344 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
e02fd71de9
commit
3e80e6ce87
|
@ -0,0 +1,775 @@
|
||||||
|
#
|
||||||
|
# = net/ntlm.rb
|
||||||
|
#
|
||||||
|
# An NTLM Authentication Library for Ruby
|
||||||
|
#
|
||||||
|
# This code is a derivative of "dbf2.rb" written by yrock
|
||||||
|
# and Minero Aoki. You can find original code here:
|
||||||
|
# http://jp.rubyist.net/magazine/?0013-CodeReview
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# Copyright (c) 2005,2006 yrock
|
||||||
|
#
|
||||||
|
# This program is free software.
|
||||||
|
# You can distribute/modify this program under the terms of the
|
||||||
|
# Ruby License.
|
||||||
|
#
|
||||||
|
# 2006-02-11 refactored by Minero Aoki
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# All protocol information used to write this code stems from
|
||||||
|
# "The NTLM Authentication Protocol" by Eric Glass. The author
|
||||||
|
# would thank to him for this tremendous work and making it
|
||||||
|
# available on the net.
|
||||||
|
# http://davenport.sourceforge.net/ntlm.html
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# Copyright (c) 2003 Eric Glass
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and distribute this document
|
||||||
|
# for any purpose and without any fee is hereby granted,
|
||||||
|
# provided that the above copyright notice and this list of
|
||||||
|
# conditions appear in all copies.
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# The author also looked Mozilla-Firefox-1.0.7 source code,
|
||||||
|
# namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
|
||||||
|
# Jonathan Bastien-Filiatrault's libntlm-ruby.
|
||||||
|
# "http://x2a.org/websvn/filedetails.php?
|
||||||
|
# repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
|
||||||
|
# The latter has a minor bug in its separate_keys function.
|
||||||
|
# The third key has to begin from the 14th character of the
|
||||||
|
# input string instead of 13th:)
|
||||||
|
#--
|
||||||
|
# $Id$
|
||||||
|
#++
|
||||||
|
|
||||||
|
require 'base64'
|
||||||
|
require 'openssl'
|
||||||
|
require 'openssl/digest'
|
||||||
|
require 'kconv'
|
||||||
|
|
||||||
|
module Net #:nodoc:
|
||||||
|
module NTLM
|
||||||
|
|
||||||
|
module VERSION #:nodoc:
|
||||||
|
MAJOR = 0
|
||||||
|
MINOR = 1
|
||||||
|
TINY = 1
|
||||||
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||||
|
end
|
||||||
|
|
||||||
|
SSP_SIGN = "NTLMSSP\0"
|
||||||
|
BLOB_SIGN = 0x00000101
|
||||||
|
LM_MAGIC = "KGS!@\#$%"
|
||||||
|
TIME_OFFSET = 11644473600
|
||||||
|
MAX64 = 0xffffffffffffffff
|
||||||
|
|
||||||
|
FLAGS = {
|
||||||
|
:UNICODE => 0x00000001,
|
||||||
|
:OEM => 0x00000002,
|
||||||
|
:REQUEST_TARGET => 0x00000004,
|
||||||
|
# :UNKNOWN => 0x00000008,
|
||||||
|
:SIGN => 0x00000010,
|
||||||
|
:SEAL => 0x00000020,
|
||||||
|
# :UNKNOWN => 0x00000040,
|
||||||
|
:NETWARE => 0x00000100,
|
||||||
|
:NTLM => 0x00000200,
|
||||||
|
# :UNKNOWN => 0x00000400,
|
||||||
|
# :UNKNOWN => 0x00000800,
|
||||||
|
:DOMAIN_SUPPLIED => 0x00001000,
|
||||||
|
:WORKSTATION_SUPPLIED => 0x00002000,
|
||||||
|
:LOCAL_CALL => 0x00004000,
|
||||||
|
:ALWAYS_SIGN => 0x00008000,
|
||||||
|
:TARGET_TYPE_DOMAIN => 0x00010000,
|
||||||
|
:TARGET_INFO => 0x00800000,
|
||||||
|
:NTLM2_KEY => 0x00080000,
|
||||||
|
:KEY128 => 0x20000000,
|
||||||
|
:KEY56 => 0x80000000
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
# module functions
|
||||||
|
class << self
|
||||||
|
def decode_utf16le(str)
|
||||||
|
Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_utf16le(str)
|
||||||
|
swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
|
||||||
|
end
|
||||||
|
|
||||||
|
def pack_int64le(val)
|
||||||
|
[val & 0x00000000ffffffff, val >> 32].pack("V2")
|
||||||
|
end
|
||||||
|
|
||||||
|
def swap16(str)
|
||||||
|
str.unpack("v*").pack("n*")
|
||||||
|
end
|
||||||
|
|
||||||
|
def split7(str)
|
||||||
|
s = str.dup
|
||||||
|
until s.empty?
|
||||||
|
(ret ||= []).push s.slice!(0, 7)
|
||||||
|
end
|
||||||
|
ret
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def apply_des(plain, keys)
|
||||||
|
dec = OpenSSL::Cipher::DES.new
|
||||||
|
keys.map {|k|
|
||||||
|
dec.key = k
|
||||||
|
dec.encrypt.update(plain)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def lm_hash(password)
|
||||||
|
keys = gen_keys password.upcase.ljust(14, "\0")
|
||||||
|
apply_des(LM_MAGIC, keys).join
|
||||||
|
end
|
||||||
|
|
||||||
|
def ntlm_hash(password, opt = {})
|
||||||
|
pwd = password.dup
|
||||||
|
unless opt[:unicode]
|
||||||
|
pwd = encode_utf16le(pwd)
|
||||||
|
end
|
||||||
|
OpenSSL::Digest::MD4.digest pwd
|
||||||
|
end
|
||||||
|
|
||||||
|
def ntlmv2_hash(user, password, target, opt={})
|
||||||
|
ntlmhash = ntlm_hash(password, opt)
|
||||||
|
userdomain = (user + target).upcase
|
||||||
|
unless opt[:unicode]
|
||||||
|
userdomain = encode_utf16le(userdomain)
|
||||||
|
end
|
||||||
|
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
|
||||||
|
end
|
||||||
|
|
||||||
|
# responses
|
||||||
|
def lm_response(arg)
|
||||||
|
begin
|
||||||
|
hash = arg[:lm_hash]
|
||||||
|
chal = arg[:challenge]
|
||||||
|
rescue
|
||||||
|
raise ArgumentError
|
||||||
|
end
|
||||||
|
chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
|
||||||
|
keys = gen_keys hash.ljust(21, "\0")
|
||||||
|
apply_des(chal, keys).join
|
||||||
|
end
|
||||||
|
|
||||||
|
def ntlm_response(arg)
|
||||||
|
hash = arg[:ntlm_hash]
|
||||||
|
chal = arg[:challenge]
|
||||||
|
chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
|
||||||
|
keys = gen_keys hash.ljust(21, "\0")
|
||||||
|
apply_des(chal, keys).join
|
||||||
|
end
|
||||||
|
|
||||||
|
def ntlmv2_response(arg, opt = {})
|
||||||
|
begin
|
||||||
|
key = arg[:ntlmv2_hash]
|
||||||
|
chal = arg[:challenge]
|
||||||
|
ti = arg[:target_info]
|
||||||
|
rescue
|
||||||
|
raise ArgumentError
|
||||||
|
end
|
||||||
|
chal = NTL::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 = 10000000 * (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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def ntlm2_session(arg, opt = {})
|
||||||
|
begin
|
||||||
|
passwd_hash = arg[:ntlm_hash]
|
||||||
|
chal = arg[:challenge]
|
||||||
|
rescue
|
||||||
|
raise ArgumentError
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# base classes for primitives
|
||||||
|
class Field
|
||||||
|
attr_accessor :active, :value
|
||||||
|
|
||||||
|
def initialize(opts)
|
||||||
|
@value = opts[:value]
|
||||||
|
@active = opts[:active].nil? ? true : opts[:active]
|
||||||
|
end
|
||||||
|
|
||||||
|
def size
|
||||||
|
@active ? @size : 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class String < Field
|
||||||
|
def initialize(opts)
|
||||||
|
super(opts)
|
||||||
|
@size = opts[:size]
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(str, offset=0)
|
||||||
|
if @active and str.size >= offset + @size
|
||||||
|
@value = str[offset, @size]
|
||||||
|
@size
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize
|
||||||
|
if @active
|
||||||
|
@value
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def value=(val)
|
||||||
|
@value = val
|
||||||
|
@size = @value.nil? ? 0 : @value.size
|
||||||
|
@active = (@size > 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
class Int16LE < Field
|
||||||
|
def initialize(opt)
|
||||||
|
super(opt)
|
||||||
|
@size = 2
|
||||||
|
end
|
||||||
|
def parse(str, offset=0)
|
||||||
|
if @active and str.size >= offset + @size
|
||||||
|
@value = str[offset, @size].unpack("v")[0]
|
||||||
|
@size
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize
|
||||||
|
[@value].pack("v")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Int32LE < Field
|
||||||
|
def initialize(opt)
|
||||||
|
super(opt)
|
||||||
|
@size = 4
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(str, offset=0)
|
||||||
|
if @active and str.size >= offset + @size
|
||||||
|
@value = str.slice(offset, @size).unpack("V")[0]
|
||||||
|
@size
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize
|
||||||
|
[@value].pack("V") if @active
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Int64LE < Field
|
||||||
|
def initialize(opt)
|
||||||
|
super(opt)
|
||||||
|
@size = 8
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(str, offset=0)
|
||||||
|
if @active and str.size >= offset + @size
|
||||||
|
d, u = str.slice(offset, @size).unpack("V2")
|
||||||
|
@value = (u * 0x100000000 + d)
|
||||||
|
@size
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize
|
||||||
|
[@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# base class of data structure
|
||||||
|
class FieldSet
|
||||||
|
class << FieldSet
|
||||||
|
def define(&block)
|
||||||
|
c = Class.new(self)
|
||||||
|
def c.inherited(subclass)
|
||||||
|
proto = @proto
|
||||||
|
subclass.instance_eval {
|
||||||
|
@proto = proto
|
||||||
|
}
|
||||||
|
end
|
||||||
|
c.module_eval(&block)
|
||||||
|
c
|
||||||
|
end
|
||||||
|
|
||||||
|
def string(name, opts)
|
||||||
|
add_field(name, String, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def int16LE(name, opts)
|
||||||
|
add_field(name, Int16LE, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def int32LE(name, opts)
|
||||||
|
add_field(name, Int32LE, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def int64LE(name, opts)
|
||||||
|
add_field(name, Int64LE, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def security_buffer(name, opts)
|
||||||
|
add_field(name, SecurityBuffer, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def prototypes
|
||||||
|
@proto
|
||||||
|
end
|
||||||
|
|
||||||
|
def names
|
||||||
|
@proto.map{|n, t, o| n}
|
||||||
|
end
|
||||||
|
|
||||||
|
def types
|
||||||
|
@proto.map{|n, t, o| t}
|
||||||
|
end
|
||||||
|
|
||||||
|
def opts
|
||||||
|
@proto.map{|n, t, o| o}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def add_field(name, type, opts)
|
||||||
|
(@proto ||= []).push [name, type, opts]
|
||||||
|
define_accessor name
|
||||||
|
end
|
||||||
|
|
||||||
|
def define_accessor(name)
|
||||||
|
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
||||||
|
def #{name}
|
||||||
|
self['#{name}'].value
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{name}=(val)
|
||||||
|
self['#{name}'].value = val
|
||||||
|
end
|
||||||
|
End
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize
|
||||||
|
@alist.map{|n, f| f.serialize }.join
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(str, offset=0)
|
||||||
|
@alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def size
|
||||||
|
@alist.inject(0){|sum, a| sum += a[1].size}
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](name)
|
||||||
|
a = @alist.assoc(name.to_s.intern)
|
||||||
|
raise ArgumentError, "no such field: #{name}" unless a
|
||||||
|
a[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
def []=(name, val)
|
||||||
|
a = @alist.assoc(name.to_s.intern)
|
||||||
|
raise ArgumentError, "no such field: #{name}" unless a
|
||||||
|
a[1] = val
|
||||||
|
end
|
||||||
|
|
||||||
|
def enable(name)
|
||||||
|
self[name].active = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def disable(name)
|
||||||
|
self[name].active = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Blob = FieldSet.define {
|
||||||
|
int32LE :blob_signature, {:value => BLOB_SIGN}
|
||||||
|
int32LE :reserved, {:value => 0}
|
||||||
|
int64LE :timestamp, {:value => 0}
|
||||||
|
string :challenge, {:value => "", :size => 8}
|
||||||
|
int32LE :unknown1, {:value => 0}
|
||||||
|
string :target_info, {:value => "", :size => 0}
|
||||||
|
int32LE :unknown2, {:value => 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
SecurityBuffer = FieldSet.define {
|
||||||
|
int16LE :length, {:value => 0}
|
||||||
|
int16LE :allocated, {:value => 0}
|
||||||
|
int32LE :offset, {:value => 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SecurityBuffer
|
||||||
|
attr_accessor :active
|
||||||
|
def initialize(opts)
|
||||||
|
super()
|
||||||
|
@value = opts[:value]
|
||||||
|
@active = opts[:active].nil? ? true : opts[:active]
|
||||||
|
@size = 8
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(str, offset=0)
|
||||||
|
if @active and str.size >= offset + @size
|
||||||
|
super(str, offset)
|
||||||
|
@value = str[self.offset, self.length]
|
||||||
|
@size
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize
|
||||||
|
super if @active
|
||||||
|
end
|
||||||
|
|
||||||
|
def value
|
||||||
|
@value
|
||||||
|
end
|
||||||
|
|
||||||
|
def value=(val)
|
||||||
|
@value = val
|
||||||
|
self.length = self.allocated = val.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def data_size
|
||||||
|
@active ? @value.size : 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Message < FieldSet
|
||||||
|
class << Message
|
||||||
|
def parse(str)
|
||||||
|
m = Type0.new
|
||||||
|
m.parse(str)
|
||||||
|
case m.type
|
||||||
|
when 1
|
||||||
|
t = Type1.parse(str)
|
||||||
|
when 2
|
||||||
|
t = Type2.parse(str)
|
||||||
|
when 3
|
||||||
|
t = Type3.parse(str)
|
||||||
|
else
|
||||||
|
raise ArgumentError, "unknown type: #{m.type}"
|
||||||
|
end
|
||||||
|
t
|
||||||
|
end
|
||||||
|
|
||||||
|
def decode64(str)
|
||||||
|
parse(Base64.decode64(str))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_flag?(flag)
|
||||||
|
(self[:flag].value & FLAGS[flag]) == FLAGS[flag]
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_flag(flag)
|
||||||
|
self[:flag].value |= FLAGS[flag]
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump_flags
|
||||||
|
FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize
|
||||||
|
deflag
|
||||||
|
super + security_buffers.map{|n, f| f.value}.join
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode64
|
||||||
|
Base64.encode64(serialize).gsub(/\n/, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
def decode64(str)
|
||||||
|
parse(Base64.decode64(str))
|
||||||
|
end
|
||||||
|
|
||||||
|
alias head_size size
|
||||||
|
|
||||||
|
def data_size
|
||||||
|
security_buffers.inject(0){|sum, a| sum += a[1].data_size}
|
||||||
|
end
|
||||||
|
|
||||||
|
def size
|
||||||
|
head_size + data_size
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def security_buffers
|
||||||
|
@alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def deflag
|
||||||
|
security_buffers.inject(head_size){|cur, a|
|
||||||
|
a[1].offset = cur
|
||||||
|
cur += a[1].data_size
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def data_edge
|
||||||
|
security_buffers.map{ |n, f| f.active ? f.offset : size}.min
|
||||||
|
end
|
||||||
|
|
||||||
|
# sub class definitions
|
||||||
|
|
||||||
|
Type0 = Message.define {
|
||||||
|
string :sign, {:size => 8, :value => SSP_SIGN}
|
||||||
|
int32LE :type, {:value => 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type1 = Message.define {
|
||||||
|
string :sign, {:size => 8, :value => SSP_SIGN}
|
||||||
|
int32LE :type, {:value => 1}
|
||||||
|
int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
|
||||||
|
security_buffer :domain, {:value => "", :active => false}
|
||||||
|
security_buffer :workstation, {:value => "", :active => false}
|
||||||
|
string :padding, {:size => 0, :value => "", :active => false }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Type1
|
||||||
|
class << Type1
|
||||||
|
def parse(str)
|
||||||
|
t = new
|
||||||
|
t.parse(str)
|
||||||
|
t
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(str)
|
||||||
|
super(str)
|
||||||
|
enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
|
||||||
|
enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
|
||||||
|
super(str)
|
||||||
|
if ( (len = data_edge - head_size) > 0)
|
||||||
|
self.padding = "\0" * len
|
||||||
|
super(str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Type2 = Message.define{
|
||||||
|
string :sign, {:size => 8, :value => SSP_SIGN}
|
||||||
|
int32LE :type, {:value => 2}
|
||||||
|
security_buffer :target_name, {:size => 0, :value => ""}
|
||||||
|
int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE2]}
|
||||||
|
int64LE :challenge, {:value => 0}
|
||||||
|
int64LE :context, {:value => 0, :active => false}
|
||||||
|
security_buffer :target_info, {:value => "", :active => false}
|
||||||
|
string :padding, {:size => 0, :value => "", :active => false }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Type2
|
||||||
|
class << Type2
|
||||||
|
def parse(str)
|
||||||
|
t = new
|
||||||
|
t.parse(str)
|
||||||
|
t
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(str)
|
||||||
|
super(str)
|
||||||
|
if has_flag?(:TARGET_INFO)
|
||||||
|
enable(:context)
|
||||||
|
enable(:target_info)
|
||||||
|
super(str)
|
||||||
|
end
|
||||||
|
if ( (len = data_edge - head_size) > 0)
|
||||||
|
self.padding = "\0" * len
|
||||||
|
super(str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def response(arg, opt = {})
|
||||||
|
usr = arg[:user]
|
||||||
|
pwd = arg[:password]
|
||||||
|
if usr.nil? or pwd.nil?
|
||||||
|
raise ArgumentError, "user and password have to be supplied"
|
||||||
|
end
|
||||||
|
|
||||||
|
if opt[:workstation]
|
||||||
|
ws = opt[:workstation]
|
||||||
|
else
|
||||||
|
ws = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
if opt[:client_challenge]
|
||||||
|
cc = opt[:client_challenge]
|
||||||
|
else
|
||||||
|
cc = rand(MAX64)
|
||||||
|
end
|
||||||
|
cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
|
||||||
|
opt[:client_challenge] = cc
|
||||||
|
|
||||||
|
if has_flag?(:OEM) and opt[:unicode]
|
||||||
|
usr = NTLM::decode_utf16le(usr)
|
||||||
|
pwd = NTLM::decode_utf16le(pwd)
|
||||||
|
ws = NTLM::decode_utf16le(ws)
|
||||||
|
opt[:unicode] = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if has_flag?(:UNICODE) and !opt[:unicode]
|
||||||
|
usr = NTLM::encode_utf16le(usr)
|
||||||
|
pwd = NTLM::encode_utf16le(pwd)
|
||||||
|
ws = NTLM::encode_utf16le(ws)
|
||||||
|
opt[:unicode] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
tgt = self.target_name
|
||||||
|
ti = self.target_info
|
||||||
|
|
||||||
|
chal = self[:challenge].serialize
|
||||||
|
|
||||||
|
if opt[:ntlmv2]
|
||||||
|
ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, tgt, opt), :challenge => chal, :target_info => ti}
|
||||||
|
lm_res = NTLM::lmv2_response(ar, opt)
|
||||||
|
ntlm_res = NTLM::ntlmv2_response(ar, opt)
|
||||||
|
elsif has_flag?(:NTLM2_KEY)
|
||||||
|
ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
|
||||||
|
lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
|
||||||
|
else
|
||||||
|
lm_res = NTLM::lm_response(pwd, chal)
|
||||||
|
ntlm_res = NTLM::ntlm_response(pwd, chal)
|
||||||
|
end
|
||||||
|
|
||||||
|
Type3.create({
|
||||||
|
:lm_response => lm_res,
|
||||||
|
:ntlm_response => ntlm_res,
|
||||||
|
:domain => tgt,
|
||||||
|
:user => usr,
|
||||||
|
:workstation => ws,
|
||||||
|
:flag => self.flag
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Type3 = Message.define{
|
||||||
|
string :sign, {:size => 8, :value => SSP_SIGN}
|
||||||
|
int32LE :type, {:value => 3}
|
||||||
|
security_buffer :lm_response, {:value => ""}
|
||||||
|
security_buffer :ntlm_response, {:value => ""}
|
||||||
|
security_buffer :domain, {:value => ""}
|
||||||
|
security_buffer :user, {:value => ""}
|
||||||
|
security_buffer :workstation, {:value => ""}
|
||||||
|
security_buffer :session_key, {:value => "", :active => false }
|
||||||
|
int64LE :flag, {:value => 0, :active => false }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Type3
|
||||||
|
class << Type3
|
||||||
|
def parse(str)
|
||||||
|
t = new
|
||||||
|
t.parse(str)
|
||||||
|
t
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(arg, opt ={})
|
||||||
|
t = new
|
||||||
|
t.lm_response = arg[:lm_response]
|
||||||
|
t.ntlm_response = arg[:ntlm_response]
|
||||||
|
t.domain = arg[:domain]
|
||||||
|
t.user = arg[:user]
|
||||||
|
t.workstation = arg[:workstation]
|
||||||
|
|
||||||
|
if arg[:session_key]
|
||||||
|
t.enable(:session_key)
|
||||||
|
t.session_key = arg[session_key]
|
||||||
|
end
|
||||||
|
if arg[:flag]
|
||||||
|
t.enable(:session_key)
|
||||||
|
t.enable(:flag)
|
||||||
|
t.flag = arg[:flag]
|
||||||
|
end
|
||||||
|
t
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,174 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
require 'test/unit'
|
||||||
|
require 'net/ntlm'
|
||||||
|
require 'rex/socket'
|
||||||
|
|
||||||
|
class ConnectionTest < Test::Unit::TestCase
|
||||||
|
def setup
|
||||||
|
@user = "admin"
|
||||||
|
@pass = "1234"
|
||||||
|
@domain = ""
|
||||||
|
@host = "192.168.145.161"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_socket_connectivity
|
||||||
|
assert_nothing_raised do
|
||||||
|
socket = Rex::Socket.create_tcp(
|
||||||
|
'PeerHost' => @host,
|
||||||
|
'PeerPort' => 80
|
||||||
|
)
|
||||||
|
assert_kind_of Socket, socket
|
||||||
|
assert !socket.closed?
|
||||||
|
socket.close
|
||||||
|
assert socket.closed?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def http_message(msg)
|
||||||
|
get_req = "GET / HTTP/1.1\r\n"
|
||||||
|
get_req += "Host: #{@host}\r\n"
|
||||||
|
get_req += "Authorization: NTLM #{msg.encode64}\r\n"
|
||||||
|
get_req += "\r\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def client_auth(pw)
|
||||||
|
msg_1 = Net::NTLM::Message::Type1.new
|
||||||
|
get_req = http_message(msg_1)
|
||||||
|
socket = Rex::Socket.create_tcp(
|
||||||
|
'PeerHost' => @host,
|
||||||
|
'PeerPort' => 80
|
||||||
|
)
|
||||||
|
socket.put get_req
|
||||||
|
res = socket.get(3)
|
||||||
|
assert res =~ /WWW-Authenticate: NTLM TlRM/
|
||||||
|
res_ntlm = res.match(/WWW-Authenticate: NTLM ([A-Z0-9\x2b\x2f=]+)/i)[1]
|
||||||
|
assert_operator res_ntlm.size, :>=, 24
|
||||||
|
msg_2 = Net::NTLM::Message.decode64(res_ntlm)
|
||||||
|
assert msg_2
|
||||||
|
msg_3 = msg_2.response({:user => @user, :password => pw}, {:ntlmv2 => true})
|
||||||
|
assert msg_3
|
||||||
|
auth_req = http_message(msg_3)
|
||||||
|
socket.put auth_req
|
||||||
|
auth_res = socket.get(3)
|
||||||
|
socket.close
|
||||||
|
return auth_res
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_client_auth_success
|
||||||
|
assert_equal client_auth(@pass)[0,12], "HTTP/1.1 200"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_client_auth_fail
|
||||||
|
assert_not_equal client_auth("badpass")[0,12], "HTTP/1.1 200"
|
||||||
|
assert_equal client_auth("badpass")[0,12], "HTTP/1.1 401"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# FunctionTest by Minero Aoki
|
||||||
|
|
||||||
|
class FunctionTest < Test::Unit::TestCase #:nodoc:
|
||||||
|
def setup
|
||||||
|
@passwd = "SecREt01"
|
||||||
|
@user = "user"
|
||||||
|
@domain = "domain"
|
||||||
|
@challenge = ["0123456789abcdef"].pack("H*")
|
||||||
|
@client_ch = ["ffffff0011223344"].pack("H*")
|
||||||
|
@timestamp = 1055844000
|
||||||
|
@trgt_info = [
|
||||||
|
"02000c0044004f004d00410049004e00" +
|
||||||
|
"01000c00530045005200560045005200" +
|
||||||
|
"0400140064006f006d00610069006e00" +
|
||||||
|
"2e0063006f006d000300220073006500" +
|
||||||
|
"72007600650072002e0064006f006d00" +
|
||||||
|
"610069006e002e0063006f006d000000" +
|
||||||
|
"0000"
|
||||||
|
].pack("H*")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lm_hash
|
||||||
|
ahash = ["ff3750bcc2b22412c2265b23734e0dac"].pack("H*")
|
||||||
|
assert_equal ahash, Net::NTLM::lm_hash(@passwd)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ntlm_hash
|
||||||
|
ahash = ["cd06ca7c7e10c99b1d33b7485a2ed808"].pack("H*")
|
||||||
|
assert_equal ahash, Net::NTLM::ntlm_hash(@passwd)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ntlmv2_hash
|
||||||
|
ahash = ["04b8e0ba74289cc540826bab1dee63ae"].pack("H*")
|
||||||
|
assert_equal ahash, Net::NTLM::ntlmv2_hash(@user, @passwd, @domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lm_response
|
||||||
|
ares = ["c337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56"].pack("H*")
|
||||||
|
assert_equal ares, Net::NTLM::lm_response(
|
||||||
|
{
|
||||||
|
:lm_hash => Net::NTLM::lm_hash(@passwd),
|
||||||
|
:challenge => @challenge
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ntlm_response
|
||||||
|
ares = ["25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6"].pack("H*")
|
||||||
|
ntlm_hash = Net::NTLM::ntlm_hash(@passwd)
|
||||||
|
assert_equal ares, Net::NTLM::ntlm_response(
|
||||||
|
{
|
||||||
|
:ntlm_hash => ntlm_hash,
|
||||||
|
:challenge => @challenge
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lmv2_response
|
||||||
|
ares = ["d6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344"].pack("H*")
|
||||||
|
assert_equal ares, Net::NTLM::lmv2_response(
|
||||||
|
{
|
||||||
|
:ntlmv2_hash => Net::NTLM::ntlmv2_hash(@user, @passwd, @domain),
|
||||||
|
:challenge => @challenge
|
||||||
|
},
|
||||||
|
{ :client_challenge => @client_ch }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ntlmv2_response
|
||||||
|
ares = [
|
||||||
|
"cbabbca713eb795d04c97abc01ee4983" +
|
||||||
|
"01010000000000000090d336b734c301" +
|
||||||
|
"ffffff00112233440000000002000c00" +
|
||||||
|
"44004f004d00410049004e0001000c00" +
|
||||||
|
"53004500520056004500520004001400" +
|
||||||
|
"64006f006d00610069006e002e006300" +
|
||||||
|
"6f006d00030022007300650072007600" +
|
||||||
|
"650072002e0064006f006d0061006900" +
|
||||||
|
"6e002e0063006f006d00000000000000" +
|
||||||
|
"0000"
|
||||||
|
].pack("H*")
|
||||||
|
assert_equal ares, Net::NTLM::ntlmv2_response(
|
||||||
|
{
|
||||||
|
:ntlmv2_hash => Net::NTLM::ntlmv2_hash(@user, @passwd, @domain),
|
||||||
|
:challenge => @challenge,
|
||||||
|
:target_info => @trgt_info
|
||||||
|
},
|
||||||
|
{
|
||||||
|
:timestamp => @timestamp,
|
||||||
|
:client_challenge => @client_ch
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ntlm2_session
|
||||||
|
acha = ["ffffff001122334400000000000000000000000000000000"].pack("H*")
|
||||||
|
ares = ["10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455"].pack("H*")
|
||||||
|
session = Net::NTLM::ntlm2_session(
|
||||||
|
{
|
||||||
|
:ntlm_hash => Net::NTLM::ntlm_hash(@passwd),
|
||||||
|
:challenge => @challenge
|
||||||
|
},
|
||||||
|
{ :client_challenge => @client_ch }
|
||||||
|
)
|
||||||
|
assert_equal acha, session[0]
|
||||||
|
assert_equal ares, session[1]
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue