Несколько лет назад в Великую пятницу на форуме техподдержки было опубликовано сообщение о возможности взлома любого аккаунта на сервисе. Представитель компании попросил продемонстрировать это на примере его собственного аккаунта, и через несколько минут ему был задан новый пароль и создан новый плейлист. Это немедленно привлекло внимание нескольких сотрудников, вынужденных провести Пасху в попытках закрыть «дыру». В связи с особенностями уязвимости регистрация новых аккаунтов была временно закрыта.
Хакер действовал следующим образом: при желании взломать аккаунт с именем, скажем, 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, в который не входит ни один из символов ᴮᴵᴳᴮᴵᴿᴰ.
Уязвимость сначала была исправлена посредством обязательности выполнения условия X==canonical_username(X)
. Позже была добавлена функция, которая, по сути, лишь выполняла функцию канонизации и отказывала в регистрации, если old_prepare(old_prepare(name)) != old_prepare(name)
. Проблема же в Twisted была исправлена в версии 11.0.0, и, как оказалось, баг проявлялся лишь при обновлении версии Пайтона с 2.4 к 2.5, что было вызвано изменениями в стандартной библиотеке.
Подобные случаи в лишний раз подчёркивают необходимость проверки вводимых пользователем данных и избегания негатива при общении между пользователями и сотрудниками. Нашедшие уязвимость хакеры были награждены несколькими месяцами бесплатной премиум-подписки, и это был был не первый и не последний случай проблем с особенностями обработки символов. Также не стоит забывать о том, что обновления не всегда сулят избавление от багов ошибок и иногда порождают новые.
ссылка на оригинал статьи http://habrahabr.ru/post/183878/
Добавить комментарий