Skip to content
This repository has been archived by the owner on Nov 24, 2023. It is now read-only.

Latest commit

 

History

History

estates

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Центр обработки дольщиков

Автор: Калан

Сервис предлагает регистрироваться и совместно участвовать в долевом строительстве юрт. Внутри — ничего особенного, типичное CRUD-приложение на питоне с некоторой бизнес-логикой.

Некоторая информация — а именно, описание строящихся юрт и описание пользователей — считается конфиденциальной, проверяющая система записывает туда флаги.

Уязвимость I

Конфиденциальные данные содержатся в описании пользователя, причём описания чужих пользователей недоступны для просмотра — можно смотреть только своё.

Проверяется это так:

    async def user_page(request):
        user = get_user(request)
        ...
           "description": description if user_id in user else None,
        ...

Проверка на in настораживает: неужели переменная user может содержать несколько пользователей? И действительно: функция get_user, достающая залогиненного пользователя из куки, возвращает множество, которое, по-видимому, может содержать несколько пользователей, если в куке они перечислены через запятую:

def get_user(request):
    ...
        return {int(i) for i in d.split(",")}
    ...

Подпись (хеш от строки с прибавлением случайной константы) обмануть не получится. Однако следует посмотреть повнимательнее, какие именно данные попадают в куку:

    async def login(request):
        user = get_user(request)
        ...
        if user_id:
            user.add(user_id[0][0])
            ... 
            resp.set_cookie("urtlogin", get_user_str(user))
            ...

И как эта кука генерируется:

def get_user_str(user):
    return str(sum(user)) + " " + hashlib.sha1((str(sum(user)) + RB).encode()).hexdigest()

Здесь user — это множество чисел, и sum(user) просто-напросто суммирует идентификаторы как числа, а не перечисляет их через запятую, как можно было бы подумать. Таким образом, если мы можем войти сначала под учётной записью с идентификатором, например, 2, а потом, не выходя, войти под учётной записью 3, то в куке окажется строка не 2,3, а 5 — и если 5-й пользователь существует, будут видны его конфиденциальные данные: описание и список юрт.

Уязвимость не даст доступа к информации пользователей, зарегистрированных раньше, чем те, которые зарегистрированы нами, но это несущественно: проверяющая система каждый раз регистрирует нового пользователя. Так что можно заранее заготовить несколько учётных записей и суммировать их в любой нужный номер. Множественная авторизация позволяет использовать каждое слагаемое несколько раз, однако слагаемое не должно быть равно текущей сумме (если мы, имея в куке 4, попробуем зайти от пользователя 4, операция add не добавит в множество вторую четвёрку, и в куке по-прежнему останется значение 4).

Самый простой способ гарантировать себе доступ ко всем будущим учётным записям — зарегистрировать все номера от какого-нибудь $n$ до $2n$ включительно. Тогда будущие учётные записи будут иметь номера $k \ge 2n + 1$ — и можно будет представить любое такое $k$ в виде $k = n + n + ... + n + n_0$, где $n_0 \in (n, 2n]$ (помним, что хотя бы одно слагаемое должно отличаться).

Эксплоит, перебирающий пользователей

Как исправлять

  1. Выдавать создаваемым пользователям идентификаторы, которые невозможно перебрать — например, длинные случайные числа.
  2. Альтернативный вариант: сделать корректной или вовсе отключить множественную авторизацию.

Уязвимость II

Уязвимость касается описания строящейся юрты. Любой авторизованный пользователь может просматривать любую юрту — однако списка юрт нет, так что для просмотра юрты надо узнать её идентификатор.

Можно получить идентификатор случайной юрты с помощью ссылки «случайная юрта», однако в течение каждой минуты ссылка ведёт на одну и ту же юрту, и только в начале следующей минуты можно получить другую.

Идентификаторы создаваемым юртам присваиваются такие:

    async def join_urt(request):
        ...
        if urt_id == 0:
            ...
            t = int(time.time())
            ...

Таким образом, юрта получит идентификатор, равный округлённому до целого числа текущему Unix-времени (порядка 1606200000). Эти идентификаторы можно без труда перебрать, особенно зная текущее время — перебор всех юрт за последние несколько минут будет совсем лёгким.

Ещё из такого способа создания идентификаторов следует, что во всей системе можно создавать только одну юрту в секунду. Тем самым можно, в начале каждой секунды создавая в чужом сервисе юрту, предотвратить создание юрты проверяющей системой (и, соответственно, начисление очков команде за работающий сервис).

Эксплоит, перебирающий юрты

Альтернативный способ получить список юрт — выкачивать информацию о пользователях (эксплуатируя уязвимость I).

Как исправлять

  1. Юртам тоже выдавать идентификаторы, не допускающие перебора.
  2. Функциональность «случайная юрта» направить на какую-нибудь юрту, чьё описание не содержит флага (действительно случайную или конкретную), либо отключить вовсе.
  3. Не показывать список юрт на странице пользователя.