mssql login : enable windows authentification and add an encrypion check through tds prelogin mechanism

see issue #402 for some comment on the implementation and the todo's


git-svn-id: file:///home/svn/framework3/trunk@12154 4d416f70-5f16-0410-b530-b9f4589650da
unstable
amaloteaux 2011-03-27 00:24:19 +00:00
parent 25ca59b56f
commit 08df8179cc
2 changed files with 443 additions and 155 deletions

View File

@ -1,5 +1,9 @@
require 'msf/core'
require 'msf/core/exploit/mssql_commands'
require 'rex/proto/ntlm/crypt'
require 'rex/proto/ntlm/constants'
require 'rex/proto/ntlm/utils'
module Msf
@ -14,6 +18,42 @@ module Exploit::Remote::MSSQL
include Exploit::Remote::Udp
include Exploit::Remote::Tcp
#
# Constants
#
# Ntlm
NTLM_CRYPT = Rex::Proto::NTLM::Crypt
NTLM_CONST = Rex::Proto::NTLM::Constants
NTLM_UTILS = Rex::Proto::NTLM::Utils
# Encryption
ENCRYPT_OFF = 0x00 #Encryption is available but off.
ENCRYPT_ON = 0x01 #Encryption is available and on.
ENCRYPT_NOT_SUP = 0x02 #Encryption is not available.
ENCRYPT_REQ = 0x03 #Encryption is required.
# Paquet Type
TYPE_SQL_BATCH = 1 # (Client) SQL command
TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused)
TYPE_RPC = 3 # (Client) RPC
TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters,
# Request Completion, Error and Info Messages, Attention Acknowledgement
TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention
TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data
TYPE_TRANSACTION_MANAGER_REQUEST = 14 # (Client) Transaction request manager
TYPE_TDS7_LOGIN = 16 # (Client) Login
TYPE_SSPI_MESSAGE = 17 # (Client) Login
TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 7
# Status
STATUS_NORMAL = 0x00
STATUS_END_OF_MESSAGE = 0x01
STATUS_IGNORE_EVENT = 0x02
STATUS_RESETCONNECTION = 0x08 # TDS 7.1+
STATUS_RESETCONNECTIONSKIPTRAN = 0x10 # TDS 7.3+
#
# Creates an instance of a MSSQL exploit module.
#
@ -27,6 +67,7 @@ module Exploit::Remote::MSSQL
Opt::RPORT(1433),
OptString.new('USERNAME', [ false, 'The username to authenticate as', 'sa']),
OptString.new('PASSWORD', [ false, 'The password for the specified username', '']),
OptBool.new('USE_WINDOWS_AUTHENT', [ true, 'Use windows authentification', false]),
], Msf::Exploit::Remote::MSSQL)
register_advanced_options(
[
@ -198,7 +239,7 @@ module Exploit::Remote::MSSQL
#
# Send and receive using TDS
#
def mssql_send_recv(req, timeout=15)
def mssql_send_recv(req, timeout=15, check_status = true)
sock.put(req)
# Read the 8 byte header to get the length and status
@ -215,7 +256,7 @@ module Exploit::Remote::MSSQL
end
# Is this the last buffer?
if(head[1,1] == "\x01")
if(head[1,1] == "\x01" or not check_status )
done = true
end
@ -241,6 +282,92 @@ module Exploit::Remote::MSSQL
Rex::Text.to_unicode(pass).unpack('C*').map {|c| (((c & 0x0f) << 4) + ((c & 0xf0) >> 4)) ^ 0xa5 }.pack("C*")
end
#
#this method send a prelogin packet and check if encryption is off
#
def mssql_prelogin(enc_error=false)
pkt = ""
pkt_hdr = ""
pkt_data_token = ""
pkt_data = ""
pkt_hdr = [
TYPE_PRE_LOGIN_MESSAGE, #type
STATUS_END_OF_MESSAGE, #status
0x0000, #length
0x0000, # SPID
0x00, # PacketID
0x00 #Window
]
version = [0x55010008,0x0000].pack("Vv")
encryption = ENCRYPT_NOT_SUP # off
instoptdata = "MSSQLServer\0"
threadid = "\0\0" + Rex::Text.rand_text(2)
idx = 21 # size of pkt_data_token
pkt_data_token << [
0x00, # Token 0 type Version
idx , # VersionOffset
version.length, # VersionLength
0x01, # Token 1 type Encryption
idx = idx + version.length, # EncryptionOffset
0x01, # EncryptionLength
0x02, # Token 2 type InstOpt
idx = idx + 1, # InstOptOffset
instoptdata.length, # InstOptLength
0x03, # Token 3 type Threadid
idx + instoptdata.length, # ThreadIdOffset
0x04, # ThreadIdLength
0xFF
].pack("CnnCnnCnnCnnC")
pkt_data << pkt_data_token
pkt_data << version
pkt_data << encryption
pkt_data << instoptdata
pkt_data << threadid
pkt_hdr[2] = pkt_data.length + 8
pkt = pkt_hdr.pack("CCnnCC") + pkt_data
resp = mssql_send_recv(pkt)
idx = 0
while resp[0,1] != "\xff" and resp.length > 5
token = resp.slice!(0,5)
token = token.unpack("Cnn")
idx -= 5
if token[0] == 0x01
idx += token[1]
break
end
end
if idx > 0
encryption_mode = resp[idx,1].unpack("C")[0]
else
#force to ENCRYPT_NOT_SUP and hope for the best
encryption_mode = ENCRYPT_NOT_SUP
end
if encryption_mode != ENCRYPT_NOT_SUP and enc_error
raise RuntimeError,"Encryption is not supported"
end
encryption_mode
end
#
# This method connects to the server over TCP and attempts
# to authenticate with the supplied username and password
@ -251,85 +378,235 @@ module Exploit::Remote::MSSQL
disconnect if self.sock
connect
pkt = ""
idx = 0
# Send a prelogin packet and check that encryption is not enabled
if mssql_prelogin() != ENCRYPT_NOT_SUP
print_error("Encryption is not supported")
return false
end
if datastore['USE_WINDOWS_AUTHENT']
pkt << [
0x00000000, # Dummy size
0x71000001, # TDS Version
0x00000000, # Size
0x00000007, # Version
rand(1024+1), # PID
0x00000000, # ConnectionID
0xe0, # Option Flags 1
0x03, # Option Flags 2
0x00, # SQL Type Flags
0x00, # Reserved Flags
0x00000000, # Time Zone
0x00000000 # Collation
].pack('VVVVVVCCCCVV')
idx = 0
pkt = ''
pkt_hdr = ''
pkt_hdr = [
TYPE_TDS7_LOGIN, #type
STATUS_END_OF_MESSAGE, #status
0x0000, #length
0x0000, # SPID
0x01, # PacketID (unused upon specification
# but ms network monitor stil prefer 1 to decode correctly, wireshark don't care)
0x00 #Window
]
pkt << [
0x00000000, # Size
0x71000001, # TDS Version
0x00000000, # Dummy Size
0x00000007, # Version
rand(1024+1), # PID
0x00000000, # ConnectionID
0xe0, # Option Flags 1
0x83, # Option Flags 2
0x00, # SQL Type Flags
0x00, # Reserved Flags
0x00000000, # Time Zone
0x00000000 # Collation
].pack('VVVVVVCCCCVV')
cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) #application and library name
sname = Rex::Text.to_unicode( rhost )
dname = Rex::Text.to_unicode( db )
# use a default value for now
ntlmsspblob = NTLM_UTILS::make_ntlmssp_blob_init('WORKGROUP','WORKSTATION', 0xa2080205)
idx = pkt.size + 50 # lengths below
pkt << [idx, cname.length / 2].pack('vv')
idx += cname.length
pkt << [0, 0].pack('vv') # User length offset must be 0
pkt << [0, 0].pack('vv') # Password length offset must be 0
pkt << [idx, aname.length / 2].pack('vv')
idx += aname.length
pkt << [idx, sname.length / 2].pack('vv')
idx += sname.length
pkt << [0, 0].pack('vv') # unused
pkt << [idx, aname.length / 2].pack('vv')
idx += aname.length
pkt << [idx, 0].pack('vv') # locales
pkt << [idx, 0].pack('vv') #db
# ClientID (should be mac address)
pkt << Rex::Text.rand_text(6)
# NTLMSSP
pkt << [idx, ntlmsspblob.length].pack('vv')
idx += ntlmsspblob.length
pkt << [idx, 0].pack('vv') # AtchDBFile
pkt << cname
pkt << aname
pkt << sname
pkt << aname
pkt << ntlmsspblob
# Total packet length
pkt[0,4] = [pkt.length].pack('V')
pkt_hdr[2] = pkt.length + 8
pkt = pkt_hdr.pack("CCnnCC") + pkt
# Rem : One have to set check_status to false here because sql server sp0 (and maybe above)
# has a strange behavior that differs from the specifications
# upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header
# is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification
resp = mssql_send_recv(pkt,15, false)
# Extract the NTLM challenge key the lazy way
cidx = resp.index("NTLMSSP\x00\x02\x00\x00\x00")
#add exception and ...
#if (cidx == -1)
# raise XCEPT::NTLM2MissingChallenge
#end
#for the moment :
raise Error,"No challenge" if not cidx
resp.slice!(0,cidx)
challenge_key = resp[24, 8]
#!!!TEMPORARY SOLUTION !!! (copy/paste from smb/client.rb)!!!
# Until ntlm proto will have a wrapper in lib/msf and will be integrated in smb and here
# we will use ntlm2_session response only and hope for the best ... (no ntlmv2 , no signing for now ...)
client_challenge = Rex::Text.rand_text(8)
argntlm = {
:ntlm_hash => NTLM_CRYPT::ntlm_hash(pass),
:challenge => challenge_key
}
optntlm = { :client_challenge => client_challenge}
resp_ntlm = NTLM_CRYPT::ntlm2_session(argntlm,optntlm).join[24,24]
# Generate the fake LANMAN hash
resp_lm = client_challenge + ("\x00" * 16)
ntlmssp = NTLM_UTILS.make_ntlmssp_blob_auth('WORKGROUP','WORKSTATION', user, resp_lm, resp_ntlm,
'', 0xa2080205)
# Create an SSPIMessage
idx = 0
pkt = ''
pkt_hdr = ''
pkt_hdr = [
TYPE_SSPI_MESSAGE, #type
STATUS_END_OF_MESSAGE, #status
0x0000, #length
0x0000, # SPID
0x01, # PacketID
0x00 #Window
]
pkt_hdr[2] = ntlmssp.length + 8
pkt = pkt_hdr.pack("CCnnCC") + ntlmssp
resp = mssql_send_recv(pkt)
cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
uname = Rex::Text.to_unicode( user )
pname = mssql_tds_encrypt( pass )
aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
sname = Rex::Text.to_unicode( rhost )
dname = Rex::Text.to_unicode( db )
#SQL Server Authentification
else
idx = 0
pkt = ''
pkt << [
0x00000000, # Dummy size
idx = pkt.size + 50 # lengths below
0x71000001, # TDS Version
0x00000000, # Size
0x00000007, # Version
rand(1024+1), # PID
0x00000000, # ConnectionID
0xe0, # Option Flags 1
0x03, # Option Flags 2
0x00, # SQL Type Flags
0x00, # Reserved Flags
0x00000000, # Time Zone
0x00000000 # Collation
].pack('VVVVVVCCCCVV')
pkt << [idx, cname.length / 2].pack('vv')
idx += cname.length
pkt << [idx, uname.length / 2].pack('vv')
idx += uname.length
cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
uname = Rex::Text.to_unicode( user )
pname = mssql_tds_encrypt( pass )
aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
sname = Rex::Text.to_unicode( rhost )
dname = Rex::Text.to_unicode( db )
pkt << [idx, pname.length / 2].pack('vv')
idx += pname.length
idx = pkt.size + 50 # lengths below
pkt << [idx, aname.length / 2].pack('vv')
idx += aname.length
pkt << [idx, cname.length / 2].pack('vv')
idx += cname.length
pkt << [idx, sname.length / 2].pack('vv')
idx += sname.length
pkt << [idx, uname.length / 2].pack('vv')
idx += uname.length
pkt << [0, 0].pack('vv')
pkt << [idx, pname.length / 2].pack('vv')
idx += pname.length
pkt << [idx, aname.length / 2].pack('vv')
idx += aname.length
pkt << [idx, aname.length / 2].pack('vv')
idx += aname.length
pkt << [idx, 0].pack('vv')
pkt << [idx, sname.length / 2].pack('vv')
idx += sname.length
pkt << [idx, dname.length / 2].pack('vv')
idx += dname.length
pkt << [0, 0].pack('vv')
# The total length has to be embedded twice more here
pkt << [
0,
0,
0x12345678,
0x12345678
].pack('vVVV')
pkt << [idx, aname.length / 2].pack('vv')
idx += aname.length
pkt << cname
pkt << uname
pkt << pname
pkt << aname
pkt << sname
pkt << aname
pkt << dname
pkt << [idx, 0].pack('vv')
# Total packet length
pkt[0,4] = [pkt.length].pack('V')
pkt << [idx, dname.length / 2].pack('vv')
idx += dname.length
# Embedded packet lengths
pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2
# The total length has to be embedded twice more here
pkt << [
0,
0,
0x12345678,
0x12345678
].pack('vVVV')
# Packet header and total length including header
pkt = "\x10\x01" + [pkt.length + 8].pack('n') + [0].pack('n') + [1].pack('C') + "\x00" + pkt
pkt << cname
pkt << uname
pkt << pname
pkt << aname
pkt << sname
pkt << aname
pkt << dname
resp = mssql_send_recv(pkt)
# Total packet length
pkt[0,4] = [pkt.length].pack('V')
# Embedded packet lengths
pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2
# Packet header and total length including header
pkt = "\x10\x01" + [pkt.length + 8].pack('n') + [0].pack('n') + [1].pack('C') + "\x00" + pkt
resp = mssql_send_recv(pkt)
end
info = {:errors => []}
info = mssql_parse_reply(resp,info)

View File

@ -116,6 +116,26 @@ class Utils
return blob
end
# BLOB without GSS usefull for ntlmssp type 1 message
def self.make_ntlmssp_blob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201)
blob = "NTLMSSP\x00" +
[1, flags].pack('VV') +
[
domain.length, #length
domain.length, #max length
32
].pack('vvV') +
[
name.length, #length
name.length, #max length
domain.length + 32
].pack('vvV') +
domain + name
return blob
end
# GSS BLOB usefull for ntlmssp type 1 message
def self.make_ntlmssp_secblob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201)
@ -135,22 +155,7 @@ class Utils
) +
"\xa2" + self.asn1encode(
"\x04" + self.asn1encode(
"NTLMSSP\x00" +
[1, flags].pack('VV') +
[
domain.length, #length
domain.length, #max length
32
].pack('vvV') +
[
name.length, #length
name.length, #max length
domain.length + 32
].pack('vvV') +
domain + name
make_ntlmssp_blob_init(domain, name, flags)
)
)
)
@ -161,33 +166,6 @@ class Utils
end
# GSS BLOB usefull for ntlmssp type 2 message
def self.make_ntlmssp_secblob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
blob =
"\xa1" + self.asn1encode(
"\x30" + self.asn1encode(
"\xa0" + self.asn1encode(
"\x0a" + self.asn1encode(
"\x01"
)
) +
"\xa1" + self.asn1encode(
"\x06" + self.asn1encode(
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
)
) +
"\xa2" + self.asn1encode(
"\x04" + self.asn1encode(
make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
)
)
)
)
return blob
end
# BLOB without GSS usefull for ntlm type 2 message
def self.make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
@ -219,10 +197,35 @@ class Utils
return blob
end
# GSS BLOB usefull for ntlmssp type 2 message
def self.make_ntlmssp_secblob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
blob =
"\xa1" + self.asn1encode(
"\x30" + self.asn1encode(
"\xa0" + self.asn1encode(
"\x0a" + self.asn1encode(
"\x01"
)
) +
"\xa1" + self.asn1encode(
"\x06" + self.asn1encode(
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
)
) +
"\xa2" + self.asn1encode(
"\x04" + self.asn1encode(
make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
)
)
)
)
# GSS BLOB Usefull for ntlmssp type 3 message
def self.make_ntlmssp_secblob_auth(domain, name, user, lm, ntlm, enc_session_key, flags = 0x080201)
return blob
end
# BLOB without GSS Usefull for ntlmssp type 3 message
def self.make_ntlmssp_blob_auth(domain, name, user, lm, ntlm, enc_session_key, flags = 0x080201)
lm ||= "\x00" * 24
ntlm ||= "\x00" * 24
@ -232,59 +235,67 @@ class Utils
session = enc_session_key
ptr = 64
blob = "NTLMSSP\x00" +
[ 3 ].pack('V') +
[ # Lan Manager Response
lm.length,
lm.length,
(ptr)
].pack('vvV') +
[ # NTLM Manager Response
ntlm.length,
ntlm.length,
(ptr += lm.length)
].pack('vvV') +
[ # Domain Name
domain_uni.length,
domain_uni.length,
(ptr += ntlm.length)
].pack('vvV') +
[ # Username
user_uni.length,
user_uni.length,
(ptr += domain_uni.length)
].pack('vvV') +
[ # Hostname
name_uni.length,
name_uni.length,
(ptr += user_uni.length)
].pack('vvV') +
[ # Session Key (none)
session.length,
session.length,
(ptr += name_uni.length)
].pack('vvV') +
[ flags ].pack('V') +
lm +
ntlm +
domain_uni +
user_uni +
name_uni +
session + "\x00"
return blob
end
# GSS BLOB Usefull for ntlmssp type 3 message
def self.make_ntlmssp_secblob_auth(domain, name, user, lm, ntlm, enc_session_key, flags = 0x080201)
blob =
"\xa1" + self.asn1encode(
"\x30" + self.asn1encode(
"\xa2" + self.asn1encode(
"\x04" + self.asn1encode(
"NTLMSSP\x00" +
[ 3 ].pack('V') +
[ # Lan Manager Response
lm.length,
lm.length,
(ptr)
].pack('vvV') +
[ # NTLM Manager Response
ntlm.length,
ntlm.length,
(ptr += lm.length)
].pack('vvV') +
[ # Domain Name
domain_uni.length,
domain_uni.length,
(ptr += ntlm.length)
].pack('vvV') +
[ # Username
user_uni.length,
user_uni.length,
(ptr += domain_uni.length)
].pack('vvV') +
[ # Hostname
name_uni.length,
name_uni.length,
(ptr += user_uni.length)
].pack('vvV') +
[ # Session Key (none)
session.length,
session.length,
(ptr += name_uni.length)
].pack('vvV') +
[ flags ].pack('V') +
lm +
ntlm +
domain_uni +
user_uni +
name_uni +
session + "\x00"
make_ntlmssp_blob_auth(domain, name, user, lm, ntlm, enc_session_key, flags )
)
)
)