# Sad Rendel ![](https://i.imgur.com/Zkik8H7.png) > Создано при поддержке моего [канала](https://t.me/lasagnahowto) [~~Ссылка на таск~~ без vpn конфига не работает](http://sad-rendel.tasks.2021.ctf.cs.msu.ru) [И его сорцы](https://github.com/uvicorn/writeups/tree/master/Sad_Rendel/app) ## Немного истории Таск был взят с [Moscow School Ctf 2021](https://2021.ctf.cs.msu.ru/challenges), на которой я играл в команде Shadow Kitties (8 место). Удивлен, что его решило всего 2 команды. ![](https://i.imgur.com/9UWiDLf.png) Решил я только этот таск. В свое оправдание скажу, что в тот день писал олимпиаду Верченко по криптографии, пол дня провел в городе и приехал домой мертвый. ## Решение Заглянем в [main.py](https://github.com/uvicorn/writeups/blob/master/Sad_Rendel/app/main.py) и обнаружим условие получения флага. ```python from cookies import encrypt_cookie, decrypt_cookie def get_user(request): cookie = request.cookies.get('sess') if cookie is None: return None session_data = decrypt_cookie(cookie) return int(session_data["user_id"]) async def get_flag(request): user = get_user(request) if user is None: return templates.TemplateResponse('flag_page.html', { 'request': request, 'flag': 'Flag is not available to anonymous users' }) if user == 1: return templates.TemplateResponse('flag_page.html', { 'request': request, 'flag': FLAG }) else: return templates.TemplateResponse('flag_page.html', { 'request': request, 'flag': 'Only admin can read a flag' }) ``` Разобравшись, что get_user берет информацию о пользователе из куки и возвращает его user_id, используя при этом функцию decrypt_cookie, идем в [cookies.py](https://github.com/uvicorn/writeups/blob/master/Sad_Rendel/app/cookies.py) к этой самой функции. ```python def decrypt_cookie(encrypted_cookie): envelope_data = json.loads(base64.b64decode(encrypted_cookie)) iv = base64.b64decode(envelope_data['iv']) cbc = AES.new(KEY2, AES.MODE_CBC, iv) second_layer = base64.b64decode(envelope_data['data']) first_layer = cbc.decrypt(second_layer) nonce = base64.b64decode(envelope_data['nonce']) ctr = AES.new(KEY1, AES.MODE_CTR, nonce=nonce) data = ctr.decrypt(first_layer) return json.JSONDecoder().raw_decode(data.decode('utf8', errors='replace'))[0] ``` Так как нам доступен `IV`, мы можем провести атаку bit flip на `CBC`. ![](https://i.imgur.com/luW4e8O.png) Суть в том, что `P = Dec(C) ⊕ IV`, соответственно мы можем подменить часть `IV` (в нашем случае 1 байт, потому что user_id занимает именно столько), чтобы получить желаемый плейнтекст (нам известен исходный `P` , поэтому проблем не возникнет). Второй этап расшифрования не представляет угрозы, так как при `CTR` шифротекст не используется в функции `Dec`. ![](https://i.imgur.com/hLK3C6o.png) Итак, заходим в бурп, логинимся, берем куку, подменяем в ней `IV` и забираем флаг. Вот код решения. ```python= import json from base64 import b64decode, b64encode iv_user_id_index = json.dumps({"user_id":9}).index('9') # всегда 12 def bit_flip(cook, user_id=0): user_id ^= 1 recv_json = json.loads(b64decode(cook).decode()) iv = recv_json['iv'] iv = list(b64decode(iv)) iv[iv_user_id_index] ^= user_id recv_json['iv'] = b64encode(bytes(iv)).decode() return b64encode(json.dumps(recv_json).encode()) cook = input() user_id = int(input()) print(bit_flip(cook, user_id).decode()) ``` Опа флаг ![](https://i.imgur.com/7TtzCN9.png) > Я до конца думал, что это unintended решение (иначе зачем класть таск на крипту вместе с тасками на веб), но содержимое флага развеяло сомнения ## Эпилог Мой первый райтап, мог написать дичь, не стесняйтесь критиковать.