Эти чертовы инкрементальные айдишники

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

    Пример №1.
    Сайт крупнейшего агрегатора платежных методов в России, обслуживает лидера онлайн-игр. После оплаты заказа переадресовывает клиента на урл вида aggregator-domain/ok.php?payment_id=123456, который в свою очередь переадресовывает на сайт онлайн-игры с адресом вида (декодировал для читабельности) online-game-domain/shop/?...amount=32.86...&currency=RUB...&user=user_email@gmail.com...&item_name=1 день премиум аккаунта...
    Перебирая значения параметра payment_id, мы можем видеть логины юзеров в онлайн-игре, покупки, которые они совершали, их сумму.



    Пример №2.
    На одном из активно рекламируемых сайтов по выдаче онлайн кредитов после заполнения формы с личными данными, результат сформированной заявки выдавался по ссылке вида domain/confirmation?clientId=12345, где при GET запросе возвращались, помимо всего, ФИО, адрес проживания, идентификационный номер налогоплательщика.
    Эти данные нужно было передать на клиента для формирования счета на оплату через кассу банка. Но если перебирать значения параметра clientId, мы получали персональные данные других клиентов.



    Пример №3.
    Интернет-банк одного из крупнейших банков страны. При формировании регулярного платежа ему присваивался инкрементальный айдишник и по POST запросу на адрес вида domain/calendar_event.php с параметром id=12345 возвращалась подробная информация о созданном регулярном платеже, включая ФИО пользователя, номера карт, сумму платежа, назначение, регулярность. И опять же, при изменении параметра id в меньшую или большую сторону мы получали доступ к приватной информации других клиентов.



    Пример №4.
    Довольно популярный чат для сайтов. Учетная запись имела настройки, историю диалогов, администрирование операторов и пр.
    Идентификатор сессии имел вид sessionID=”12345|6749476c44696255216a6148516d5e33”
    где первая часть — идентификатор компании, вторая — идентификатор пользователя.
    Как результат, при изменении первого идентификатора мы попадали в админку совершенно чужой компании. Получив доступ к аккаунту, мы имели возможность добавлять операторов и вести диалог с клиентами от имени кампании.



    Вывод:
    В приведенных выше примерах из-за ошибок программистов была допущена потенциальная возможность утечки персональных данных. Это большая цена за удобство использование целочисленного идентификатора.
    Что делать? Используйте токен. Произвольная строка даже небольшой длины защитит вас от перебора и массовой кражи информации. Ведь вы не можете защититься от ошибок программистов, а код, который построен на использовании инкрементальных идентификаторов, придется постоянно контролировать

    P.S.
    Соглашусь с комментариями и добавлю от себя:
    Использование токена подходит только как дополнительный уровень безопасности, полноценная защита должна быть реализована в архитектуре приложения через систему прав и доступов.
    Поделиться публикацией
    Комментарии 93
    • НЛО прилетело и опубликовало эту надпись здесь
        –25
        Знаете, как это случается? Везде проверяли, а здесь забыли. Просто взяли новенького, а он ещё плохо знаком с кодом.
          +45
          > Просто взяли новенького, а он ещё плохо знаком с кодом.
          А что за конторы, в которых новенького сажают на финансы?
          Огласите список — спасёте множество людей от раскрытия персональных данных.

          И, да, +1 голос в оправдание инкрементальных айдишников: не виновны.
            +1
            Вы путаете «новенький в программировании» и «новенький конкретно в этом коде»
            +11
            Code review, тем более на таких важных участках кода, еще никто не отменял.
              +13
              Имею стойкое мнение, что «кто-то рано или поздно забудет проверить права» — архитектурная ошибка системы. Решается встраиванием обязательного шага проверки доступа на уровне объекта сразу после получения этого объекта. Интерфейс к данным у нас унифицирован, вот прямо в него и встраиваем логику безопасности. В итоге имеем всю логику защиты в одном месте, логику приложения — в другом месте. И люди, которые пишут логику приложения, просто не смогут влезть в логику безопасности и что-то там сломать. К сожалению, по моему личному опыту, такие вещи замечает и придумывает архитектор системы, а никак не программисты, которые её делают. А нанимать архитекторов в этой роли для enterprise-разработки компании не хотят. В итоге имеем повсеместные костыли, антипаттерны и оверинженернг. Что и приводит к вышеописанной ситуации.
                0
                >> Решается встраиванием обязательного шага проверки доступа на уровне объекта сразу после получения этого объекта

                Что у вас понимается под объектом? Модель? Если так, то это неверно. Модель не может знать, кто к нему имеет доступ. Безопасность — функция контроллера.
                  +1
                  Одноранговый MVC для таких вещей — недостаточно широкий подход, в том и дело. С точки зрения логики приложения данные получаются из модели, но и сама модель в свою очередь реализует MVC или другой подход, в который можно встроить контроль доступа без проблем. У нас принято называть такую схему «конвейер». Вход одной подсистемы это выход другой подсистемы. Каждая подсистема исполняет свою роль и не более того.

                  В данном случае конвейер начинается с интерфейса к данным, после этого проходит проверка доступа, после этого вывод пользователю. Хитрость в том, чтобы не давать делать пути в обход конвейера.
              0
              Кстати на 2-х из этих сайтов проверка id была, но не везде
              • НЛО прилетело и опубликовало эту надпись здесь
                  +8
                  Сессия, очевидно.
                  • НЛО прилетело и опубликовало эту надпись здесь
                      +1
                      Не морочьте голову. Для определения сессии куки не обязательны, а отправить ссылку на страницу «спасибо за заказ» Вам наврядли понадобится.
                      • НЛО прилетело и опубликовало эту надпись здесь
                          +2
                          А по поводу ссылки на сделанный заказ, зря вы так, бывает удобно, например, при букинге номера в отеле отправить ссылку своим друзьям с данными брони

                          Всегда можно сделать такую страничку не привязанную к конкретному заказу, т.е. что-то вроде страницы-конструктора, где на вход подаются все данные, необходимые для ее отображения.
                          site/booking?hotel_id=5&room=34&guests=3&sum=5000
                          • НЛО прилетело и опубликовало эту надпись здесь
                        +1
                        вместо куки можно использовать local или sessionStorage
                    +1
                    А еще есть такая штука как тесты. Помогает!!!
                      0
                      +100500!

                      Единственная причина, почему инкрементальные айдишники могут не использоваться явно — это скрыть реальное количество пользователей/счетов/заявок и т.п. Других причин просто нет, RBAC вам в помощь.

                      P.S.: ну и, само-собой, в русле autoincrement и mySQL могут быть и другие проблемы, но это не имеет отношения к тому, про что статья.
                      +23
                      Не вижу проблемы в этом, просто нормальные программисты проверяют права доступа к данным под этим ID у текущей учетки пользователя.
                        –8
                        Рано или поздно кто-то забудет проверить права.
                          +16
                          Как будто не нужно проверять права для ID'шников без последовательной генерации.
                            –6
                            Конечно нужно, но вероятность подбора подходящего ID, если проверку забыли выполнить, намного ниже
                              0
                              Это вы так думаете. На самом же деле, если там какой-нибудь random() из стандартной библиотеки, то подбор делается очень легко.
                            +4
                            А это свидетельство лапшеархетиктуры. Программист при реализации метода на сервере вообще не должен думать о правах. Всё должно быть проверено выше и автоматически.
                            • НЛО прилетело и опубликовало эту надпись здесь
                                +5
                                Выше, это до вызова соотв. метода.
                                На роутах, в базовом контроллере — от архитектуры зависит, но смысл как раз в том. что сначала проверка прав, затем — вызов метода.
                                  0
                                  Как вы проверите вызов метода типа showRecord(recordId) на принадлежность пользователю, если запись из базы вы ещё не прочитали?
                                    0
                                    Не позову showRecord(recordId), а позову метод getDocumentInfo(uid, docId).
                                      0
                                      И где права будете проверять?
                                        +1
                                        Я — не буду.

                                        Проверять будет метод getDocumentInfo(uid, docId).

                                        Нельзя работать с данными напрямую, если вызов вашей функции осуществляет клиент. Вместо этого необходимо использовать слой абстракции, контролирующий доступ. Неважно — uid, токен или что-то еще.
                                          0
                                          Мы разве не о разработке этого метода и говорим?
                                            0
                                            Тогда вы правы. В этом случае выбрать запись придется обязательно.
                                            При этом не должно быть кода, позволяющего выбирать запись мимо данного метода — что, собственно, и было названо «лапшеархитектурой».
                                      0
                                      Опять же, от архитектуры зависит.
                                      Я в базовом контроллере смогу проверить более глобальные вещи, например, имеет ли пользователь доступ к всей части сайта (авторизировался ли он), имеет ли права на просмотр записей (например если у него нет записей или он залогинился как редактор, который к данным записям вообще не должен доступ иметь).

                                      Но да, вы правы, проверить принадлежит ли запись конкретному пользователю без выборки соотв. записи не получится.
                                    +16
                                    Выше — это выше по стеку вызовов. Как-то так:
                                    • НЛО прилетело и опубликовало эту надпись здесь
                                        +4
                                        истинно так, хорошая картинка
                                          +1
                                          Теперь я точно знаю, зачем нужна кнопка «добавить комментарий в избранное».
                                            0
                                            А что за тула для построения таких шикарных сиквенс диаграмм была использована?
                                              0
                                              Тоже заинтересовался, спросил у fspare еще днем, оказалось, что картинка была найдена на просторах интернета. Попробовал поискать по гугл-картинкам, не нашел. Тоже хотелось бы рисовать такие шикарные сиквенс диаграммы.
                                                +1
                                                Попробуйте gliffy, это конечно не оно, но тоже очень даже
                                          +1
                                          В общем случае это невозможно, когда дело касается доступа не на уровне таблицы или столбца (в терминологии РСУБД), а конкретных строк. Чтобы узнать есть ли у пользователя право доступа к той или иной записи нужно прочитать эту запись и узнать принадлежит ли она пользователю. Есть, конечно, варианты, но они ниже метода, на уровне системы хранения данных, а не выше.
                                      +3
                                      Я не думаю, что это прямо ошибки программистов, очень часто рабочий процесс поставлен так, что хороший программист работает быстро, а плохой медленно. Заботе же о безопасности и престиже компании внимания практически не уделяется.

                                      Совсем недавно писал в один крупный российский интернет магазин, что у них купоны с автоикриментом, уже 2 месяца прошло, ничего не меняли.
                                        –3
                                        Можно вторым параметром захэшить ID…

                                        Кстати, немного сбило столку предложение:
                                        Ведь вы не можете защититься от ошибок программистов, а код, который построен на использовании инкрементальных идентификаторов, прийдется постоянно контролировать
                                          0
                                          Я имел ввиду, что даже если есть проверка доступа по ID, её могут забыть заюзать, и нужен постоянный кодревью на такие ошибки
                                            +1
                                            Вообще, моя идея была в том, чтобы в простом примере (без ролей, прав, рефереров, ip), была возможность простого отсева запросов, и предложенный вариант включал в себя 2 параметра: сам ID, и хэш от id.string, таким образом, не прибегая к большим правкам (срочно?), попытки перебора id становятся бесмысленными, т.к. нет возможности подобрать верный хэш.
                                              +1
                                              UPD: а то, что кодеры лепят невесть чего, это уже их проблема, а не ID.
                                                +1
                                                ВКонтакте картинки, например, так и работают, даже в личных сообщениях.
                                            +25
                                            Я по инкрементальным айдишникам обычно оцениваю оборот магазинов :) Вообще, это достаточно ценная бизнес информация даже при закрытых вопросах с безопасностью и не стоит ее так явно выставлять наружу.
                                              +4
                                              Поздравляем, заказ #03c7c0ace395d80182db07ae2c30f034 оплачен
                                                +10
                                                Продиктуйте, пожалуйста, номер заказа по телефону
                                                  +2
                                                  Открываю почту, одно из первых сообщений:

                                                  Your order FO3743C95A14 has been updated by elizza


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

                                                  Номер заказа типа A9GDM5 — сложно?
                                                    +2
                                                    *Подумалось*
                                                    Можно даже поизвращаться с человеко-запоминаемыми идентификаторами типа «HeeQui», «JooShu».
                                                    Ну, чтобы смешнее было.
                                                      0
                                                      Угу, а потом кому-то из клиентов сгенерируется нецензурный идентификатор, и он раструбит на весь интернет, какой хамский у вас магазин.
                                                        +4
                                                        Может быть еще веселее.

                                                        На корпоративном блогохостинге MS пару лет назад был забавный инцидент — работник-китаец никак не мог запостить анонс продукта в блог своей собственной команды.

                                                        Как выяснилось в результате разбора полетов, на хостинге был список нецензурных слов, который — поскольку блоги там на самых разных языках — покрывал все эти языки скопом. Включая всевозможные транслит-варианты.

                                                        Имя у китайца было — Hui (очень часто встречающееся среди китайцев, кстати)…
                                                      –1
                                                      Вообще, сложно…

                                                      А как их вообще генерировать?
                                                      Достаточно ли шестизнака?
                                                      Не будет в конце периода стабильного таймаута при проверке коллизий/перегенерировании.
                                                      Проанализировать законодательство — некоторые типы документов должны иметь непрерывную нумерацию.
                                                      Проанализировать бизнес-процесс — по внутренним регламентам должна быть непрерывная нумерация.
                                                      Нужно учесть возможные разночтения этого ID (о и нуль)
                                                      Возможно, этот ID где-то линкуется с другими ID, например ID склада, что вызовет другие разночтения.
                                                      Возможно, другие системы, с которыми осуществляется обмен данными не поддерживают буквенные символы, учитывают/не учитывают/преобразуют регистр и т.п.
                                                        +1
                                                        Вы сейчас описали абсолютно стандартные и разрешимые вопросы, которые перед каждым разработчиком или проектировщиком возникают ежедневно и которые он должен уметь решать.

                                                        Самая жесть с которой сталкивался я лично — настроенный инкрементальный ID сущности, который использовался в качестве первичного ключа. А потом ВНЕЗАПНО оказывалось, что тот же ID нужно присвоить какому-то еще документу (например, номера приказов начинаются заново с нового года). И все, финиш, перелопачивать структуру базы. А все потому, что голову нужно всегда включать.
                                                          0
                                                          Мда, номер приказа использовать в качестве первичного ключа… это ж офигеть.
                                                            0
                                                            Называется «естественный первичный ключ». Для нормализованной базы это нормально. Суррогатные первичные ключи обычно избыточны.
                                                              0
                                                              Только это часть от него. Обычно у документов номера с нового года начинаются заново, и в разных подразделениях может быть своя независимая от других нумерация.
                                                                0
                                                                И я о том же.
                                                                В документообороте номера документов никак первичным ключом нельзя делать.
                                                                И табельный номер у сотрудников — тоже…
                                                                  0
                                                                  Тогда естественный первичный ключ должен быть составным, например год+номер. Суррогатные просто удобнее на практике, пускай и увеличивают объёмы данных за счёт избыточности.
                                                                    0
                                                                    А потом мы в куче связанных документов меняем дату или номер и начинаем жертвовать памятью и процессором.
                                                      0
                                                      Я по инкрементальным айдишникам обычно оцениваю оборот магазинов

                                                      У меня один заказчик был, вот ему захотелось пресечь такие попытки. Поэтому мы к id-шникам добавляли 1635.
                                                      Правда там не магазин был, а сервис типа вопрос-ответ.
                                                        +2
                                                        Ну, это не защита :) Более менее простой алгоритм очень легко вычленяется при желании.
                                                        +3
                                                        Мы однажды по заказу разрабатывали интернет-магазин. Одно из требований у заказчика было сделать номера заказов чтобы они начинались не с 1, а например с 1000. И тоже самое с артикулами. Очень он хотел чтобы было так, считал что артикул товара 1 и заказ номер 3 это адски несолидно.
                                                        +5
                                                        Мы в универе так 3 года тесты проходили в некоем Moodle. Там айдишки ответов сквозные и в открытом виде прямо внутри сорца страницы были. Ну а по логике вещей, наименьшая айдишка правильная )
                                                          +10
                                                          Я не согласен с автором статьи — нужно имплементировать security правильно, а не лепить костыли.

                                                          Выбор идентификаторов должен быть диктован ограничениями системы/ее архитектурой, а не попыткой что-то там заобфусцировать/захачить/прикрутить еще один клевый хак-о-костыль.

                                                          Если Security заимплементирована криво, то это проблема дизайна приложения/архитектуры/квалификации программистов.

                                                          Взгляд допустим на тот же Spring Security дает понимание того, как оно могло бы по правильному работать. И даже если вы не работаете с Java, больше чем уверен, что есть множество решений под разные языки/платформы готовые, удовлетворяющие нуждам.
                                                          Ну или хотя бы на их основании можно в правильном направлении начать двигаться, а не лепить что-то по типа «А давайте добавим еще один ID и слепим их вместе, а потом будем разбивать их пополоам, никто ведь не догадается».
                                                            +5
                                                            Пример №2 и Пример №3

                                                            Думаю что роскомнадзор, следящий за выполнением ФЗ №152, будет очень рад провести проверку этих фирм :)
                                                              0
                                                              Думаю, что эти фирмы вне досягаемости роскомнадзора: обратите внимание на упомянутую валюту и название города.
                                                              0
                                                              Недавно тоже столкнулся с такой идеей как не дать перебрать все фотографии в системе. Ничего умнее соли не придумал. в итоге получалась ссылка: /media/640x480/abrakadabra_452
                                                                +1
                                                                GUID?
                                                                  0
                                                                  нет, совместная работа media_id и salt.
                                                                    –1
                                                                    Это предложение было :)
                                                                      –1
                                                                      … сложноподчиненное
                                                                  +1
                                                                  А какой смысл делать подобную защиту от перебора фотографий? Это защита от парсеров? Так если кому то надо будет спарсить — можно сдлать другими способами, это не проблема.
                                                                  А не придираюсь. Мне действительно интересно.
                                                                  +3
                                                                  Все ведь помнят эту историю…
                                                                    +3
                                                                    Нужно различать две проблемы:
                                                                    — возможность доступа произвольному пользователю по известному ему id к записи конфиденциальной информации
                                                                    — возможность (простота) подбора произвольным пользователем id

                                                                    Как правило, проблема именно в первом пункте, а не во втором: доступ получает любой обладатель «секретной» ссылки, хотя как фича сервиса это не задумывалось. Пытаться «засекретить» ссылку путем использования менее очевидного алгоритма её формирования проблемы не решает.
                                                                      0
                                                                      Как раз работаю в похожей системе, и у нас есть возможность посмотреть статус платежа по ID, который, можно сказать, инкрементируется, т.е. легко подбираем.
                                                                      Но это диктуется удобством пользователя, если Вы положили деньги в терминале и хотите узнать статус платежа — то Вы можете просто зайти на сайт и по номеру чека получит эту инфу. Удобно. Никаких регистраций. Да и стали бы Вы регистрироваться на сайте владельца терминала только для того, чтобы узнать статус платежа.
                                                                      С другой стороны есть проблемы с безопасностью(причем, с точки зрения менеджеров, незначительные). Не могу сказать как у нас принимали окончательное решение об этой фиче, какие варианты рассматривали, но работает оно так.
                                                                        0
                                                                        А почему бы не печатать на том же чеке код какой-нибудь, и не хранить его в той же базе где данные платежа, и выводить его при вводе правильного кода?
                                                                          0
                                                                          Да это все понятно, но сама система «растет» уже лет эдак 8, представляете какое это наследие? Да и работаем мы с кучей сторонних процессингов, терминалов. Вот так взять и запилить фичу это дороговато будет. Тут как в бойцовском клубе: пока сумма выплат по страховке меньше стоимости отзыва серии автомобилей — делать ничего не будем.
                                                                      +2
                                                                      Ну тут разные мысли могут быть. Вообще главная проблема это авторизация и разграничение доступа. Взять пример мой баянистый: habrahabr.ru/company/dsec/blog/143921/.
                                                                      Тут в Яндекс почте отсутствовала авторизация, и доступ по ID письма. Но именно тот факт, что ИД был обычным инструментируемым целым числом, можно было проводить целевые атаки на почтовый ящик. То есть шлешь письмо себе и врагу. У тебя ID = X, у врага X+1, и вот ты уже знаешь его пул сообщений и читаешь остальные письма из его ящика (при прочих удачных моментах).

                                                                      + верно заметили, что не рандомный идишник дает некоторую статистическую инфу о работе системы.

                                                                      Зависит от конкретной реализации и бизнеса. Это нужно просто понять, проанализировать и построить правильную архитектуру. Проверка субъекта при ддоступе к объекту — это главная проблема. Не рандомный идшник — вторая, возможно не такая важная, но в совокупности с первой дающая больше векторов для атаки и позволяет сделать что-то иное.
                                                                        0
                                                                        Ох, увидел вторую картинку и получил заряд бодрости на весь день. Совпадает практически вся незаблюреная инфа.
                                                                        А ещё вчерашний платёж через один из мерчантов задержался на 20 часов (при том что нормальное время проводки — 5 минут).
                                                                        В мозгу уже запускался поиск лестных эпитетов в пользу «тестировщиков», но потом дочитал, что инфа с сервиса с кредитами, коими я не пользуюсь.
                                                                        Словом, статья — пример того, как важны проверки всей информации, которая поступает в приложение извне.
                                                                          +3
                                                                          У нас в Латвии за перебор айдишников в URL устраивают маски-шоу и судят. Слава Богу, оправдали (пардон, не получается вставить ссылку на русскую википедию).
                                                                            0
                                                                            Википедия: Пойканс, Илмар
                                                                            0
                                                                            Это было отверстие в системе безопасности, такое даже дырой не назовешь. Да и там скорее намеренный слив был по политическим причинам, так как в итоге вскрылось очень много неприглядного. Вообще такие проверки нормальными разработчиками делаются на полном автомате, как только пишешь свою систему сообщений или платформу блогов.
                                                                            Встречал и более интересные ляпы — хранение файлов, к которым ограничен доступ, в одной папке под инкрементальными именами. Например, domainname/files/1.jpg, domainname/files/2.pdf и тд., даже без использования file_put_contents. Либо с использованием и передачей параметра file_id (опять же инкрементального), но без проверки того, кто и откуда за ним обращается.
                                                                            –7
                                                                            переименуйте статью в — Эти чертовы школьники
                                                                              0
                                                                              А еще есть UUID. Странно, что в банке используется инкрементный ключ. Как же тогда shared бд?
                                                                                0
                                                                                Хорошо бы было в статьеисказать, что основная причина использовали последовательных id — простота обеспечения уникальности. И привести примеры, как другими идентификаторами добиться уникальности. Знаю про GUID, какие еще варианты есть?
                                                                                  0
                                                                                  Я бы сказал, основная мотивация для последовательных id — это всё же возможность использования кластерных индексов. Непоследовательный уникальный id всегда можно сгенерировать на основе последовательного, применив какую-нибудь обратимую функцию, но выгоды никакой — и кластеризацию поломает, и нижеописанные проблемы останутся.

                                                                                  Другие проблемы:
                                                                                  * Простота последовательных id может обернуться боком, когда захочется, например, смерджить данные из двух баз в одну, и целые диапазоны первичных ключей перекроются. Особенно весело, если эти значения уже расползлись по другим базам, и уже нельзя их просто так взять и поменять.
                                                                                  * Невозможность генерить уникальный id на клиентской стороне, что несколько усложняет создание объектов — приходится вместо простого результата «успех/неуспех» возвращать ещё и id свежесозданного объекта, затем апдейтить его у клиента, что не даёт использовать immutable object pattern и загрязняет код.
                                                                                  0
                                                                                  Если на сайте не предусмотрена регистрация, но нужна ссылка на заказ, которую юзер бы мог открывать с разных компьютеров, то можно использовать следующее: если у вас «инкрементальные айдишники», чтобы кардинально не переделывать, просто добавляем к запросу параметр — результат хеширования id с солью: site.ru/shipment/2457?hash=1289ae093b… В коде просто сверяем результат хеширования и переданный хеш.

                                                                                  P.S. Также не забываем закрывать от индексации подобные страницы, а то вон сколько случаев было
                                                                                    0
                                                                                    Описанная в статья уязвимость — это разновидность Сross Site Request Forgery Лечится добавлением токенов в каждый запрос. Скажем, в Phalcon для этого есть удобные встроенные механизмы.

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

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