Взлом с помощью Юникода (на примере GitHub)

    Юникод исключительно сложен. Мало кто знает все хитрости: от невидимых символов и контрольных знаков до суррогатных пар и комбинированных эмодзи (когда при сложении двух знаков получается третий). Стандарт включает 216 кодовых позиций в 17-ти плоскостях. По сути, изучение Юникода можно сравнить с изучением отдельного языка программирования.

    Неудивительно, что веб-разработчики упускают из вида некоторые нюансы. С другой стороны, злоумышленники могут использовать особенности Юникода в своих целях, что и делают.

    Специалист по безопасности Джон Грейси продемонстрировал на примере GitHub баг проверки адреса электронной почты для восстановления забытого пароля. Подобные баги можно встретить и на других сайтах.

    Джон Грейси объясняет, что такое «коллизия трансляции знаков», когда два разных знака после конвертации транслируются в один и тот же знак.

    В данном случае он использовал турецкий символ 'ı' ('i' без точки), который транслируется в латинскую 'i', так что почтовый адрес John@Gıthub.com после обработки превращается в John@Github.com:

    'ß'.toLowerCase() // 'ss'
    'ß'.toLowerCase() === 'SS'.toLowerCase() // true
    
    // Note the Turkish dotless i
    'John@Gıthub.com'.toUpperCase() === 'John@Github.com'.toUpperCase()

    Такие коллизии можно найти по всем плоскостям Юникода: вот полный список.

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

    Знак Кодовая точка Результат
    ß 0x00DF SS
    ı 0x0131 I
    ſ 0x017F S
    0xFB00 FF
    0xFB01 FI
    0xFB02 FL
    0xFB03 FFI
    0xFB04 FFL
    0xFB05 ST
    0xFB06 ST
    0x212A k

    GitHub позволял злоумышленнику получить пароль от чужого аккаунта, потому что процедура восстановления забытого пароля работала некорректно.

    В рамках этой процедуры выполнялось сравнение введённого адреса электронной почты с адресом, который хранится в базе. Алгоритм проверки:

    1. Введённый адрес переводится в нижний регистр с помощью метода toLowerCase.
    2. Введённый адрес сравнивается с адресом в базе зарегистрированных пользователей.
    3. Если найдено совпадение, пароль из базы данных высылается на введённый адрес.

    Очевидно, разработчики не знали о коллизии трансляции адресов при использовании метода toLowerCase.

    В данном случае исправить ошибку просто. Достаточно высылать пароль не на введённый адрес, а на адрес из базы данных.

    Конечно, это не полное исправление ошибки, а только быстрый патч. Более полным решением будет трансляция в Punycode для проверки: John@Gıthub.comxn—john@gthub-2ub.com. Punycode был разработан для однозначного преобразования доменных имен в последовательность ASCII-символов. Адрес электронной почты можно проверять таким же способом, но большинство веб-приложений этого не делает.

    За найденную уязвимость Джон Грейси получил денежное вознаграждение и 2500 очков в рейтинг, хотя ему ещё далеко до главного гитхабовского хакера Александра Добкина <img src=404 onerror=alert(document.domain)>: пользователь с таким необычным именем заработал уже 30 750 очков, в том числе за выполнение произвольного кода на серверах GitHub, на которых генерируются страницы GitHub Pages.


    Сбой в мессенджере при получении эмодзи с чёрной точкой (Messenger в iOS, WhatsApp под Android)

    Связанные с Юникодом баги имеют такое свойство, что их можно встретить в любом приложении, которое обрабатывает текст, введённый пользователем. Уязвимости есть и в веб-приложениях, и в нативных программах под Android и iOS. Одним из самых известных стал баг iOS от 2015 года, когда несколько знаков Юникода в текстовом сообщении вызывали сбой операционной системы. В прошлом году похожий юникодовский баг обнаружили в iOS 11.3, он известен как «чёрная точка». Похожий сбой происходил в приложении WhatsApp под Android, если прикоснуться к эмодзи.






    GlobalSign
    Компания

    Комментарии 59

      +2
      так что почтовый адрес John@Gıthub.com после обработки превращается в John@Gıthub.com:
      видимо во втором случае должно быть John@Github.com?
        +3
        Мало кто знает все хитрости

        Спасибо за ссылку на статью! Берём на вооружение :)
          0
          Интересно, у многих ли этот адрес не открывается? Заблокирован?
            0
            Не думаю что заблокирован — через прокси тоже не открывается.
              0
              Открыл через vpn, заблокирован через ркн наверняка
              0
              В тор-бро открылось
            0
            'ß'.toLowerCase() === 'SS'.toLowerCase() // true
            У кого-нибудь это работает? У меня почему-то получается false.
            +2

            Это John@Gıthub.com → xn--john@gthub-2ub.com кажись должно быть так John@Gıthub.com → john@xn--gthub-n4a.com но punycoder делает первый вариант.

              0

              Punycode умеет только строки переводить. Он не знает где у мыла домен, а где логин. В логине может быть всё что угодно, там делать какие-либо изменения не корректно в принципе. А вот к домену какие-то требования применять можно.

              0
              А в PHP данный баг работает?

              Выполняю:
              var_dump(strtolower('ß')); // ß

              Коллизии нет
                0

                Выше написали, что автор ошибся. Нужно в верхний регистр переводить.
                PHP по умолчанию умеет работать только с кодировкой ASCII (пример: strlen('я') даст результат 2)
                Для работы с Юникодом нужно подключить плагин mbstring. И правильный пример будет такой:


                var_dump(mb_strtoupper('ß'));
                  0
                  var_dump(mb_strtoupper('ß')); //string(2) "ß"

                  Моя конфигурация mbstring
                  php -i | grep -i mbstring
                  /etc/php/7.2/cli/conf.d/20-mbstring.ini,
                  Zend Multibyte Support => provided by mbstring
                  Multibyte decoding support using mbstring => enabled
                  mbstring
                  mbstring extension makes use of "streamable kanji code filter and converter", which is distributed under the GNU Lesser General Public License version 2.1.
                  mbstring.detect_order => no value => no value
                  mbstring.encoding_translation => Off => Off
                  mbstring.func_overload => 0 => 0
                  mbstring.http_input => no value => no value
                  mbstring.http_output => no value => no value
                  mbstring.http_output_conv_mimetypes => ^(text/|application/xhtml\+xml) => ^(text/|application/xhtml\+xml)
                  mbstring.internal_encoding => no value => no value
                  mbstring.language => neutral => neutral
                  mbstring.strict_detection => Off => Off
                  mbstring.substitute_character => no value => no value

                  Что я делаю не так?

                0

                Так эта функция не работает с многобайтными кодировками, включая юникод. Используйте mb_strtolower. Но там написано что может преобразовывать, вот только не понятно правильно или нет.

                0
                del
                Вы указали в тексте решение проблемы.
                  0

                  Зачем менять регистр почты или логина?

                    0
                    Почта не регистрозависимая.
                    Мог при регистрации ввести John, а при восстановлении забыть и писать john.
                      –2

                      Адрес почты регистрозависим! По крайней мере до знака @.

                        +1

                        Нет. Локальная часть до @ интерпретируется сервером. Домен к регистру не чувствителен.
                        https://tools.ietf.org/html/rfc5321#section-2.3.11

                          +3

                          Так вы ссылку кинули как раз на документ, который говорит, что после @ по факту регистронезависим, а до собаки — зависит от реализации сервера. К чему тут "нет"?

                            0
                            нет тут к тому, что общепринято делать почту не регистрозависимой.
                              0

                              Давайте по честному. Не принято, а легче так думать и так её обрабатывать. Но из этого следует не то, что можно это считать стандартом де факто, а то, что просто всем лень её обрабатывать правильно.

                                +1
                                Лень не имеет никакого отношения к реальности в этой ситуации.
                                Просто почтовик, который разрешит регистрировать разные адреса отличающиеся только регистром и сортировать почту с учетом регистра никто не будет использовать.
                                Потому что преимуществ ровно ноль, а вот проблем посыпется с головой.
                                Зачем условному админу почты может захотеться иметь регистрозависимою почту? Просто зачем?
                                  –2

                                  За тем, что логины могут быть и не только от почты, а браться например из совершенно другой системы? Например если бы хабр решил всем раздать мыльники с @habr.com, то он бы скорее всего взял в качестве имени ящика именно ник пользователя. А если их приводить к одному регистру, то начнутся проблемы.
                                  И это синтетический.


                                  Практический юзкейс — русские имена в почте.

                                    +1
                                    русские имена в почте.

                                    Такого не должно быть потому что не должно быть.


                                    Например если бы хабр решил всем раздать мыльники с habr.com, то он бы скорее всего взял в качестве имени ящика именно ник

                                    Вы правда думаете, что ники регистрозависимы?

                                      +2
                                      Такого не должно быть потому что не должно быть.

                                      В стандарте написано, что может быть что угодно, значит там будет что угодно.


                                      Вы правда думаете, что ники регистрозависимы?

                                      Смотря где. На хабре независимые, а в другой системе может быть что угодно.

                                      +1
                                      Примеры весьма показательны тем, что абсолютно абстрактны и не имеют отношения к реальности.
                                      Даже столкнувшись с такой задачей никто в здравом уме не влезет в такое legacy. Скорее выберут какое-то компромиссное решение.
                                        +1
                                        Скорее выберут какое-то компромиссное решение.

                                        Это совершенно не значит, что какой-нибудь админ не засядет и не сделает всем русские мыльники. Точно так же это не значит, что в каком-то нормальном почтовике вдруг не реализуют полную поддержку стандарта этого.


                                        Слишком много в этом всём деле "скорее всего", "вдруг" и "никто в здравом уме". Это подход, который как раз и тянет за собой опасности по типу "никакое устройство в здравом уме не будет нам подкидывать битые пакеты". А получается из этого много и много уязвимостей.

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

                                            У вас странное понимание выражения "стандарт де факто". Для меня это когда никаких бумаг нет, но все приняли какое-то общее решение. А тут бумага есть и она является стандартом. Так что никаких де факто тут быть не может.

                                              +1
                                              Вот этот вот стандарт — это де юре.
                                              А то что сделано и используется — де факто.
                                              Хоть википедию бы открыли сначала чтоли. :)
                                +1
                                Адрес почты регистрозависим! По крайней мере до знака @.

                                Строго говоря, строка ДО знака @ НЕ является регистрозависимой или регистронезависимой. Это определено реализацией сервера. Строка ПОСЛЕ @ адресует сервер, и она является регистроНЕзависимой. Извините за мой французский.

                        0

                        Конечно, обрабатывать UNICODE не просто.


                        Но дело в том, что программисты часто обрабатывают то, чего не надо обрабатывать совсем.


                        Пример с email показателен! Почтовые адреса чувствительны к регистру! Зачем надо было преобразовать? Сравнивайте напрямую. Потребитель который хочет сменить пароль очень хорошо знает какой у него адрес!


                        И вообще, тема валидации почтовых адресов совершенно неоднозначная. И в ней правило "меньше лучше" действует на все 100%.

                          0

                          Нигде не смог найти подтверждения того, что эмейл адрес должен быть чувствителен к регистру. Можно смылку, пожалуйста?

                            +3

                            RFC-5321:


                            The standard mailbox naming convention is defined to be local-part@domain;… the local-part MUST be interpreted and assigned semantics only by the host specified in the domain part of the address.

                            Иными словами, это дело хоста решать, зависимы ли имена в его домене от регистра или нет. Клиент должен исходить из худшего, и хранить/посылать емейлы в том регистре, в каком его ввели изначально. Поиск среди сохранённых емейлов можно вести регистронезависимо.

                            0
                            Почтовые адреса чувствительны к регистру!

                            В нормальных почтовиках нет. И условный gecube@gmail.com будет то же самое, что и GECUBE@GMAIL.COM

                              0

                              Хуже того. G.E.C.U.B.E@GMAIL.COM может быть эквивалентом GECUBE@GMAIL.COM. Как и GECUBE+TAG@GMAIL.COM

                                +1
                                ЕМНИП: точки в имени это фишка гугла, а вот тэг это уже rfc
                                  0

                                  Но тем не менее — MS Exchange игнорирует обе возможности )
                                  В остальном — Вы правы )

                                    0
                                    MS Exchange полностью игнорит тэги или можно включить? Zimbra по умолчаню игнорит, но включить можно.
                                      0
                                      MS Exchange полностью игнорит тэги или можно включить?

                                      Насколько мне известно — полностью. Но я могу чего-то не знать :-)

                                  0

                                  Это не тэг а сабэдрисиз, и на них есть rfc

                                    0
                                    Спасибо. Везде встречал их как «тэги»
                                –1

                                Что это за "нормальный почтовик"????


                                Регистрозависимость решает получатель, а не отправитель! И тот клиент, который отправляет почту по собственным правилам рискует эту почту просто не доставить. Если "почтовик" не доставивший почту, хотя это было вполне возможно, "нормальный", то спасибо, но нет. Возьму ненормального.

                                  +1

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

                                    0
                                    Так об этом и речь, что если на стороне получателя почтовый сервис различает юзеров в зависимости от регистра и позволяет регистрировать одинаковые, но разные почтовые адреса, то это первый шаг в сторону ада.

                                    Ну-у-у, ад-не-ад, а стандарт этого допускает. Так что вся обработка адресов (вкл. валидация) должна соответствовать. И кстати это по сути значительно упрощает обработку и делает жизнь легче.

                              +2
                              Введённый адрес переводится в нижний регистр с помощью метода toLowerCase.
                              Введённый адрес сравнивается с адресом в базе зарегистрированных пользователей.
                              Если найдено совпадение, пароль из базы данных высылается на введённый адрес.


                              Причем тут юникод не совсем понятно. Джуновская ошибка, старая как мир.
                                +2
                                Если найдено совпадение, пароль из базы данных высылается на введённый адрес.

                                Надеюсь, что пароля в базе данных никогда не было, а высылается токен авторизации процедуры смены пароля…
                                  0

                                  Причем токен авторизации имеет срок действия )

                                  0

                                  Так, вы сказали, что в Юникоде 216 символов, а там можно хранить 231, но используются только 1 112 064. Видимо вы перппутали с размером плоскости, который как раз 2**16 (информация из Википедии)

                                    +1

                                    Такс, не учёл разметку. Имел ввиду, что в Юникоде можно хранить 2^31, а не 2^16, как вы написали.

                                    0
                                    В php такого не наблюдаю при преобразовании UTF-8 строки.

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое