Pull to refresh

Взлом аккаунта и юникодные символы

Reading time 3 min
Views 34K
В техническом блоге «Спотифая» было опубликовано интересное исследование на тему взлома аккаунтов сервиса путём использования особенностей канонизации вводимых пользователем данных. Это стало возможным благодаря тому, чем спотифаевцы гордятся, — полностью юникодному логину. К примеру, пользователь легко может иметь снеговика в качестве имени аккаунта, если он того пожелает. Реализация подобного, впрочем, с самого начала доставляла некоторые неудобства.

Несколько лет назад в Великую пятницу на форуме техподдержки было опубликовано сообщение о возможности взлома любого аккаунта на сервисе. Представитель компании попросил продемонстрировать это на примере его собственного аккаунта, и через несколько минут ему был задан новый пароль и создан новый плейлист. Это немедленно привлекло внимание нескольких сотрудников, вынужденных провести Пасху в попытках закрыть «дыру». В связи с особенностями уязвимости регистрация новых аккаунтов была временно закрыта.

Хакер действовал следующим образом: желая взломать аккаунт с именем, скажем, bigbird, он регистрировал аккаунт с именем ᴮᴵᴳᴮᴵᴿᴰ (в Пайтоне эта строчка выглядит как u’\u1d2e\u1d35\u1d33\u1d2e\u1d35\u1d3f\u1d30′). После запроса ссылки на сброс пароля задавался новый пароль, который подходил к аккаунту bigbird.

Проблема состояла в канонизации имени пользователя, некорректно обрабатывающей запрещенные и эквивалентные символы. Очевидно, что в имени пользователя недопустимы пробелы, а BigBird и bigbird — это один и тот же логин. Первый случай — это обработка запрещенных символов, второй — обработка некоторых символов как эквивалентных друг другу. Подобное достигается канонизацией имени пользователя.

Если допускаются только символы латинского алфавита (a—z, A—Z), то будет достаточно

canonical_username = username.lower()


Таким образом BigBird, Bigbird, bigbird и любые производные будут одним и тем же логином. BigBird в таком случае называют точным именем пользователя, а bigbird — каноническим. При создании аккаунта необходимо, чтобы канонический логин был свободен в системе.

Обращение в нижний регистр обладает свойством идемпотентности, то есть приложение его к одной и той же строке один и более раз даёт один и тот же результат:

x.lower() == x.lower().lower()


С разрешением юникодных символов начинаются разнообразные проблемы. К примеру, внешне трудно отличить Ω от Ω, хотя первое — буква омега, а второе — символ единицы измерения, и в Юникоде это — различные символы. Очевидно, что просто обращения в нижний регистр было бы недостаточно. К счастью для разработчиков, собственной системы канонизации разрабатывать не пришлось, в фреймворке Twisted были необходимые методы, разработанные ещё для XMPP.

from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep
def canonical_username(name):
    return nodeprep.prepare(name)


Идемпотентность обещается в спецификациях. Так в чём же дело? Посмотрим, что случается при вводе ᴮᴵᴳᴮᴵᴿᴰ.

>>> canonical_username(u'\u1d2e\u1d35\u1d33\u1d2e\u1d35\u1d3f\u1d30')
u'BIGBIRD'
>>> canonical_username(canonical_username(u'\u1d2e\u1d35\u1d33\u1d2e\u1d35\u1d3f\u1d30'))
u'bigbird'


Как видно, свойство идемпотентности не выполняется для этих символов. Дело в том, что согласно официальной документации учитывались символы Юникода 3.2, в который не входит ни один из символов ᴮᴵᴳᴮᴵᴿᴰ. При регистрации аккаунта после однократного применения функции канонизации создавался логин BIGBIRD, что было допустимо, поскольку каноническое имя не пересекалось с существующим аккаунтом bigbird. При отсылке сообщения на электронную почту с ссылкой для сброса пароля ᴮᴵᴳᴮᴵᴿᴰ канонизировался единожды, поэтому письмо получал пользователь BIGBIRD. Но при использовании ссылки функция канонизации использовалась повторно, что приводило к сбросу пароля для аккаунта bigbird, а не BIGBIRD.

Уязвимость сначала была исправлена посредством обязательности выполнения условия X==canonical_username(X). Позже была добавлена функция, которая, по сути, лишь выполняла функцию канонизации и отказывала в регистрации, если old_prepare(old_prepare(name)) != old_prepare(name). Проблема же в Twisted была исправлена в версии 11.0.0, и, как оказалось, баг проявлялся лишь при обновлении версии Пайтона с 2.4 к 2.5, что было вызвано изменениями в стандартной библиотеке.

Подобные случаи в лишний раз подчёркивают необходимость проверки вводимых пользователем данных и избегания негатива при общении между пользователями и сотрудниками. Нашедшие уязвимость хакеры были награждены несколькими месяцами бесплатной премиум-подписки, и это был не первый и не последний случай проблем с особенностями обработки символов. Также не стоит забывать о том, что обновления не всегда сулят избавление от багов и иногда порождают новые.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+80
Comments 31
Comments Comments 31

Articles