Kolejne ćwiczenie w którym „pastwimy się” nad trybem ECB i znów wykorzystujemy właściwość tego trybu, ale tym razem trochę inaczej. W trybie ECB każdy blok danych jest od siebie niezależny dlatego, dane zaszyfrowane można przemieszać i po odszyfrowaniu uzyskamy prawidłowe dane, ale w innej kolejności. Trzeba tylko zadbać o to, żeby mieszać dane bloki danych a nie pojedyncze bajty.
Zanim przejdziemy do rozwiązania samego zadania, musimy przygotować sobie środowisko, które będzie symulowało naszą wyrocznię. Zaczniemy od funkcji parsującej podany ciąg znaków i stworzy z niego słownik. Przykładowo: funkcja po dostaniu na wejściu:
foo=bar&baz=qux&zap=zazzle
Powinna zwrócić:
{
foo: 'bar',
baz: 'qux',
zap: 'zazzle'
}
Moja implementacja wygląda tak:
def urlParamsToJson(url_string):
urlVariables = url_string.split('&')
result = {}
for urlVariable in urlVariables:
tmpParams = urlVariable.split('=')
result[tmpParams[0]] = tmpParams[1]
return result
Najpierw rozbijam łańcuch wejściowy, tak żeby podzielić go na krótsze łańcuchy oddzielone od siebie znakiem &. Następnie każdy taki mniejszy ciąg rozbijany jest na dwa kolejne łańcuchy – 1. to klucz a 2. to wartość. Wszystko dodawane jest do słownika i zwracane jako rezultat. To nie jest kod produkcyjny, więc zmienne nie są sprawdzanie, zakładam że podane dane są prawidłowe i dobrze sformatowane.
Teraz czas na nasza wyrocznię. Jej zadanie jest proste. Na początku generowany jest 128-bitowy klucz szyfrujący. Potem do dyspozycji użytkownika są dostępne dwie metody:
1. getProfile – Dla podanego jako parametr adresu e-mail, dodaje uid oraz rolę, zamienia na postać: [email protected]&uid=10&role=user a następnie szyfruje AES w trybie ECB i zwraca zaszyfrowany profil. Funkcja nie pozwala aby w adresie e-mail znajdowały się znaki & oraz =, są one usuwane.
2. decodeProfile – Odszyfrowuje podany profil parsuje go przy pomocy wcześniej stworzonej funkcji i zwraca wynik.
Poniżej moja implementacja:
class OracleProfileFor(object):
M_ENCRYPTION_KEY = b''
def __init__(self):
self.M_ENCRYPTION_KEY = generateRandomData(16)
def getProfile(self, user_mail):
user_mail = user_mail.replace('&', '').replace('=', '')
tmpProfile = 'email='+user_mail+'&uid=10&role=user'
tmpProfile = addPaddingPKCS7(bytearray(tmpProfile, 'ascii'), 16)
cipher = AES.new(self.M_ENCRYPTION_KEY, AES.MODE_ECB)
return cipher.encrypt(tmpProfile)
def decodeProfile(self, profile_data):
cipher = AES.new(self.M_ENCRYPTION_KEY, AES.MODE_ECB)
plain = cipher.decrypt(profile_data)
return urlParamsToJson(plain.decode('ascii'))
Naszym zadaniem teraz jest sprawienie, że rola przypisana do naszego adresu e-mail to nie 'user’ a 'admin’. Okazuje się, że można to zrobić w bardzo prosty sposób. Wiemy, że przed zaszyfrowaniem nasz profil ma postać: [email protected]&uid=10&role=user. Jeżeli rozbijemy to na bloki o długości 16 bajtów to otrzymamy:
email=adres@mail – pierwszy blok
.com&uid=10&role – drugi blok
=userPPPPPPPPPPP – trzeci blok (P oznacza padding).
Gdybyśmy podali inny adres e-mail:
email=aaaaaaaaaa – pierwszy blok
admin’aaaaaaaaaa – drugi blok
aaaaauser@gmail. – trzeci blok
com&uid=10&role= – czwarty blok
userPPPPPPPPPPPP – piąty blok (P oznacza padding).
Teraz ostatni blok zawiera tylko rolę rpzypisaną do naszego adresu e-mail. Z kolei drugi blok zaczyna się od słowa admin! Bardzo szczęśliwy dla nas zbieg okoliczności. Gdyby tak zamienić je miejscami otrzymamy:
email=aaaaaaaaaa – pierwszy blok
userPPPPPPPPPPPP – drugi blok
aaaaauser@gmail. – trzeci blok
com&uid=10&role= – czwarty blok
admin’aaaaaaaaaa – piąty blok
Pojawia się pewnie problem, padding w ostatnim bloku jest teraz nieprawidłowy, ale przecież mamy wpływ na to jaka jest zawartość tego bloku. Nie mogą w nim występować znaki & oraz =, ale cała reszta jest akceptowalna, gdyby zamienić litery 'a’ po słowie „admin'” na bajty o wartości 0x0a to wszystko się będzie zgadzało. Oczywiście nie możemy zmienić kolejności bloków przed zaszyfrowaniem, ale to nie ma znaczeni – pisałem o tym na początku. Moje rozwiązanie tego zadania:
def s2challenge13():
print("Challenge 13 start")
oracle = OracleProfileFor()
encProf = oracle.getProfile("aaaaaaaaaaadmin'\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\[email protected]")
decProf = oracle.decodeProfile(encProf[:16]+encProf[32:-16]+encProf[16:32])
print(decProf, len(decProf))
Kod dostępny jest również tutaj: https://gitlab.com/akoltys/cryptopals .