Создано при поддержке моего канала
Ссылка на таск без vpn конфига не работает
И его сорцы
Таск был взят с Moscow School Ctf 2021, на которой я играл в команде Shadow Kitties (8 место).
Удивлен, что его решило всего 2 команды.
Заглянем в main.py и обнаружим условие получения флага.
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 к этой самой функции.
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
и забираем флаг.
Вот код решения.
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 решение (иначе зачем класть таск на крипту вместе с тасками на веб), но содержимое флага развеяло сомнения
Мой первый райтап, мог написать дичь, не стесняйтесь критиковать.