Last active
June 29, 2024 02:15
-
-
Save righettod/45d59e1d8eb83fe351a9e9aafb37f91f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import uuid | |
import binascii | |
from datetime import datetime | |
""" | |
Python3 script trying to reproduce the "Sandwich Attack: A New Way Of Brute Forcing UUIDs" | |
described on "https://versprite.com/blog/universally-unique-identifiers/". | |
""" | |
NUM_100NS_INTERVALS_SINCE_UUID_EPOCH = 122192928000000000 | |
def extract_uuid_infos(target_uuid): | |
infos = None | |
try: | |
# Verify that the parameter passed in a valid UUID | |
uuid_item = uuid.UUID(target_uuid) | |
version = uuid_item.version | |
infos = f"V{version} - '{target_uuid}' - " | |
# Extract infos based on version | |
if version == 1: | |
epch = (uuid_item.time - NUM_100NS_INTERVALS_SINCE_UUID_EPOCH) / 10000 | |
dtime = datetime.fromtimestamp(epch / 1000) | |
node_part = target_uuid.split("-")[4] | |
mac = f"{node_part[0:2]}:{node_part[2:4]}:{node_part[4:6]}:{node_part[6:8]}:{node_part[8:10]}:{node_part[10:]}".upper() | |
infos += f"Generation time '{dtime}' - Node MAC Address '{mac}' - ClockID/ClockSequence '{uuid_item.clock_seq}'." | |
elif version == 2: | |
infos += "Least significant 8 bits of the clock sequence are replaced by a 'local domain' number and least significant 32 bits of the timestamp are replaced by an integer identifier meaningful within the specified local domain." | |
elif version == 3: | |
infos += "MD5(NAMESPACE_IDENTIFIER + NAME)." | |
elif version == 4: | |
infos += "UUID could be duplicated (low chances) so manual check needed for entropy potential issues." | |
elif version == 5: | |
infos += "SHA1(NAMESPACE_IDENTIFIER + NAME)." | |
else: | |
infos += " Unknown version." | |
except Exception: | |
infos = None | |
return infos | |
# Version of the POC fixing the "clock_seq" like in the article | |
print("==== POC v1: Fixed clock_seq") | |
item_1 = str(uuid.uuid1(clock_seq=1233)) | |
item_to_find = str(uuid.uuid1(clock_seq=1233)) | |
item_2 = str(uuid.uuid1(clock_seq=1233)) | |
print(extract_uuid_infos(item_1)) | |
print(extract_uuid_infos(item_to_find)) | |
print(extract_uuid_infos(item_2)) | |
start = int(item_1.split("-")[0],16) | |
end = int(item_2.split("-")[0],16) | |
base = item_1.split("-") | |
start_date = datetime.now() | |
print(f"Delta: {end-start}") | |
print(f"Start: {start_date}") | |
for i in range(start, end): | |
base[0] = hex(i)[2:] | |
value = "-".join(base) | |
if value == item_to_find: | |
print("Item Found!") | |
break | |
end_date = datetime.now() | |
print(f"End : {end_date}") | |
delay = end_date - start_date | |
print(f"Delay: {delay.total_seconds()} seconds") | |
# Version of the POC without fixing the "clock_seq" | |
# to mimic a standart call to python UUID V1 function | |
print("==== POC v2: Random clock_seq") | |
item_1 = str(uuid.uuid1()) | |
item_to_find = str(uuid.uuid1()) | |
item_2 = str(uuid.uuid1()) | |
print(extract_uuid_infos(item_1)) | |
print(extract_uuid_infos(item_to_find)) | |
print(extract_uuid_infos(item_2)) | |
start = int(item_1.split("-")[0],16) | |
end = int(item_2.split("-")[0],16) | |
base = item_1.split("-") | |
start_date = datetime.now() | |
print(f"Delta: {end-start}") | |
print(f"Start: {start_date}") | |
tentative_count = 0 | |
i = end | |
found = False | |
# I have remarked that the identification of the item | |
# is more faster when I start for the END bound because | |
# the generation time of the searched item is more near of | |
# the last generated item | |
while not found and i > start: | |
base[0] = hex(i)[2:] | |
# 16384: During testing on clock_seq range of values, | |
# I have remarked that the clock_seq random value never reach a value >= 16384 | |
for j in range(16384): | |
# Leverage the built-in UUID v1 function to compute the "clock_seq" value | |
# for the current "clock_seq" tried. | |
# Specify a node value in order to prevent the function to retrieve the local MAC address (gain time) | |
base[3] = str(uuid.uuid1(int(base[4],16), j)).split("-")[3] | |
value = "-".join(base) | |
tentative_count += 1 | |
if value == item_to_find: | |
print("Item Found!") | |
found = True | |
i -= 1 | |
end_date = datetime.now() | |
print(f"End : {end_date}") | |
delay = end_date - start_date | |
print(f"Delay: {delay.total_seconds() } seconds ({tentative_count} tentatives performed).") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Execution example: