# Sad Rendel

> Создано при поддержке моего [канала](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 команды.

Решил я только этот таск.
В свое оправдание скажу, что в тот день писал олимпиаду Верченко по криптографии, пол дня провел в городе и приехал домой мертвый.
## Решение
Заглянем в [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`.

Суть в том, что `P = Dec(C) ⊕ IV`, соответственно мы можем подменить часть `IV` (в нашем случае 1 байт, потому что user_id занимает именно столько), чтобы получить желаемый плейнтекст (нам известен исходный `P` , поэтому проблем не возникнет).
Второй этап расшифрования не представляет угрозы, так как при `CTR` шифротекст не используется в функции `Dec`.

Итак, заходим в бурп, логинимся, берем куку, подменяем в ней `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())
```
Опа флаг

> Я до конца думал, что это unintended решение (иначе зачем класть таск на крипту вместе с тасками на веб), но содержимое флага развеяло сомнения
## Эпилог
Мой первый райтап, мог написать дичь, не стесняйтесь критиковать.