Разработчики музыкального сервиса Spotify рассказали забавную историю, как несколько лет назад пришлось приостановить регистрацию новых аккаунтов на сайте. Один из пользователей похвастался на форуме, что может угнать любой аккаунт Spotify. Модератор форума ему не поверил — и назвал имя своего аккаунта, а уже через пять минут обнаружил у себя в подписке неприятную музыку, а потом и пароль сменился.

Разработчики не могли понять, как происходит угон аккаунтов. Из описания хакера, он регистрировал новый аккаунт на сайте со специально подобранным именем Unicode, запрашивал для него смену пароля и нажимал на ссылку смены пароля в почтовом ящике. После этого менялся пароль не у него, а в аккаунте жертвы.

Spotify — один из немногих сервисов, который позволяет регистрацию аккаунтов с любыми символами Unicode. При этом известно, что многие символы выглядят похоже: например, кириллические а, р, о, с, х внешне похожи на латинские a, p, o, c, x. Или греческая буква омега похожа на символ электрического сопротивления (Ом).

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

Для канонизации символов Unicode разработчики использовали библиотеку Twisted из Python, в которой реализован стандарт канонизации XMPP nodeprep. Действительно, зачем изобретать велосипед, если разработчики Jabber уже все стандартизировали.

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'

Если функцию вызывали один раз, то возвращался один результат, а если на этом результате функцию применяли во второй раз, то возвращался уже другой результат.

В общем, именно на этом баге и была основана механика угона аккаунтов Spotify. Пользователь регистрировал новый аккаунт c использованием Unicode-символов, которые при двойной канонизации дадут такой же результат, как и аккаунт жертвы. Например, если аккаунт жертвы BigBird, то пользователь регистрировал аккаунт ᴮᴵᴳᴮᴵᴿᴰ (строка \u1d2e\u1d35\u1d33\u1d2e\u1d35\u1d3f\u1d30). После этого он запрашивал письмо со сменой пароля. При генерации ссылки для этого письма происходила только однократная канонизация, но если щелкнуть по ней — то функция вызывалась уже во второй раз. В результате, пароль изменялся на аккаунте жертвы, а не на ᴮᴵᴳᴮᴵᴿᴰ (\u1d2e\u1d35\u1d33\u1d2e\u1d35\u1d3f\u1d30).

Как выяснилось, баг был связан с тем, что библиотека Twisted не проверяла входящие символы на соответствие Unicode 3.2, как того требовал стандарт XMPP nodeprep.

Баг появился в Python 2.5, хотя еще в Python 2.4 все работало нормально. Ну и, конечно, патч добавили в Twisted с версии 11.0.0.

Хотя баг в библиотеке исправлен, эта история является наглядным примером того, с какими проблемами в безопасности могут столкнуться сервисы, которые используют Unicode.



Оставить мнение