metasploit-framework/lib/metasploit/framework/mssql/tdssslproxy.rb

128 lines
3.3 KiB
Ruby

# -*- coding: binary -*-
require 'openssl'
#
# TDSSSLProxy:
#
# SQL Server uses the TDS protocol to transmit data between clients and
# servers. Of course this sits on top of TCP.
#
# By default, the TDS payload is not encrypted. However, if "force
# encryption" is set under the SQL Server protocol properties, it will
# use SSL/TLS to encrypt the TDS data. Oddly, the entire TCP stream is
# not encrypted (as is say for HTTPS), but instead a TDS header is
# put on the front of the TLS packet. As a result, the full TLS/SSL
# setup is done within a series of TDS payloads.
#
# This "proxy" basically creates a fake SSL endpoint (s2) from which it
# can add/remove the TDS header as required. This is implemented as a
# socket pair (think, a bidirectional pipe), where the other end is s1:
#
# sslsock <-> s1 <-> s2 <-> tdssock <-> target SQL Server.
#
# (tdssock is the reference to the "sock" from the scanner module)
#
# TO DO:
#
# This enables brute force of a SQL Server which requires encryption.
# However, future updates will permit any read/write using
# mssql_send_recv() to use crypto if required and transparently to
# other MSF developers.
#
# Cheers, JH
class TDSSSLProxy
TYPE_TDS7_LOGIN = 16
TYPE_PRE_LOGIN_MESSAGE = 18
STATUS_END_OF_MESSAGE = 0x01
def initialize(sock)
@tdssock = sock
@s1, @s2 = Rex::Socket.tcp_socket_pair
end
def cleanup
@running = false
@t1.join
end
def setup_ssl
@running = true
@t1 = Thread.start { ssl_setup_thread }
ctx = OpenSSL::SSL::SSLContext.new(:SSLv23)
ctx.ciphers = "ALL:!ADH:!EXPORT:!SSLv2:!SSLv3:+HIGH:+MEDIUM"
@ssl_socket = OpenSSL::SSL::SSLSocket.new(@s1, ctx)
@ssl_socket.connect
end
def send_recv(pkt)
@ssl_socket.write(pkt)
done = false
resp = ""
while (not done)
head = @ssl_socket.read(8)
if !(head and head.length == 8)
return false
end
# Is this the last buffer?
if (head[1, 1] == "\x01" or not check_status)
done = true
end
# Grab this block's length
rlen = head[2, 2].unpack('n')[0] - 8
while (rlen > 0)
buff = @ssl_socket.read(rlen)
return if not buff
resp << buff
rlen -= buff.length
end
end
resp
end
def ssl_setup_thread
while @running do
res = select([@tdssock, @s2], nil, nil, 0.1)
if res
res[0].each do |r|
# response from SQL Server for client
if r == @tdssock
resp = @tdssock.recv(4096)
if @ssl_socket.state[0, 5] == "SSLOK"
@s2.send(resp, 0)
else
@s2.send(resp[8..-1], 0)
end
end
# request from client for SQL Server
if r == @s2
resp = @s2.recv(4096)
# SSL negotiation completed - just send it on
if @ssl_socket.state[0, 5] == "SSLOK"
@tdssock.send(resp, 0)
# Still doing SSL
else
tds_pkt_len = 8 + resp.length
pkt_hdr = ''
pkt_hdr << [TYPE_PRE_LOGIN_MESSAGE, STATUS_END_OF_MESSAGE, tds_pkt_len, 0x0000, 0x00, 0x00].pack('CCnnCC')
pkt = pkt_hdr << resp
@tdssock.send(pkt, 0)
end
end
end
end
end
@s1.close
@s2.close
end
end