Сегодня публикуем завершающую часть теории по безопасности веб-разработки, которую рассказал эксперт по информационной безопасности и преподаватель Иван Юшкевич. Он провел мастер-класс по безопасности на конференции РИТ++ на платформе hacktory.ai.
Практическую часть о том, как накрутить лайки в социальных сетях, украсть криптовалюту и получить доступ к самым большим секретам пользователей, вы можете посмотреть здесь и здесь.
Первую часть можно посмотреть по этой ссылке, а вторую — здесь. Сегодня рассказ будет о уязвимостях сериализации и обходе аутентификации.
![](https://habrastorage.org/getpro/habr/upload_files/6ca/cc2/9ad/6cacc29ad7155f552810cddcedbba1e1.jpg)
Уязвимости сериализации
Сериализация известна уже очень давно. Это процесс, который позволяет преобразовать объект в поток байтов. Обратный процесс — десериализация — восстанавливает первоначальную структуру объекта без создания отдельного текстового файла ввода/вывода.
![](https://habrastorage.org/getpro/habr/upload_files/226/627/048/226627048318005018a7af8c0f425fca.jpeg)
Эта технология крайне интересна и полезна, она облегчает сохранение состояния программы между запусками или передачу данных по сети, помогая экономить время разработчика. Мы можем представить объект в виде файла, передать его в другой сервер, он там обрабатывается таким же кодом, и мы получим на другом сервере такой же объект в памяти.
Протоколов передачи сериализованных данных довольно много:
RMI (Remote Method Invocation);
JMX (Java Management Extension);
JMS (Java Messaging System).
Иногда можно встретить сериализованные данные:
Обмен объектами между двумя приложениями, которые не связаны между собой, например, Клиент – Сервер;
Кэширование;
Cookie, API-токены;
Сохранение и восстановление объекта.
Форматы представления объекта могут быть абсолютно разными:
Бинарный: Java Serialization, Ruby Marshal, Protobuf, Thrift, Avro, MS-NRBF, Android Binder/Parcel, HOP
Смешанный: PHP Serialization, Python pickle, Binary XML/JSON
«Читаемый»: XML, JSON, YAML – мы сами можем посмотреть, что там содержится внутри
Но сама по себе сериализация не несет угроз. Как обычно, проблемы возникают, когда приложение обменивается потоками с небезопасным источником — когда мы пытаемся десериализовать какие-то недоверенные данные.
Вектора атак
Исчерпывающий материал по уязвимостям сериализации можно найти тут. Основных векторов атак, связанных с сериализацией, ровно два:
Нарушения логики работы программы;
Удаленное выполнение кода/команд (в некоторых случаях).
В первую очередь это так называемый Circle of Trust: данные, которые мы сериализуем, мы получаем в каком-нибудь бинарном формате. Из этого могут появляться предположения типа «бинарный формат непонятен, его сложно подделать, значит, это безопасно». Но на деле всё не так, так как сериализация — это всего лишь формат представления данных.
Например, мы сериализовали некоторый объект (здесь — Java-класс). Записав его в файл, скорее всего, мы увидим какие-то бинарные данные, где явно будет ничего не понять.
Однако если начать разбирать формат этих данных, то видно, что всё довольно структурировано и понятно, просто нужно почитать спецификацию. Например, у ZIP-архива этот заголовок тоже отличается. По двум байтам можно это понять.
![](https://habrastorage.org/getpro/habr/upload_files/331/23a/46e/33123a46eb43626ee73868bf44febf00.jpeg)
Если дальше разбирать этот формат, то увидим, что можно найти значение и поменять его. На этом и основываются все уязвимости, связанные с сериализацией. Если посмотреть историю, то огромное количество разнообразных библиотек, серьезных веб-приложений, серверов приложений, и даже сами фреймворки содержали эти проблемы.
![](https://habrastorage.org/getpro/habr/upload_files/9f3/fa5/de2/9f3fa5de27996910972a7c4da7381a8e.jpeg)
Вернемся к нашему сериализуемому объекту. У нас есть некоторый класс, который отвечает за имя пользователя в системе.
Cookie: user=base64(serialized(User.class))
У этого класса есть поле name (string) и поле userIsAdmin (boolean). Важный момент: в данных, которые сериализуются, сериализуются только значения полей. Мы не можем сериализовать сам код функции. Поэтому единственное, на что можно опираться – это изменение значения этих полей.
![](https://habrastorage.org/getpro/habr/upload_files/bf4/ac1/4b6/bf4ac14b6d3cdf1a4f21e7e7270ccf99.jpeg)
Если мы сериализуем объект, то после передачи некоторого значения сервер нам напишет: «Hello, Steph!». При этом в качестве сериализованного объекта будет как раз значение поля steph. А значение байта 00 указывает на значение переменной boolean. Но поскольку эти данные приходят от пользователя, ничто нам не мешает поменять их, например, на 1:
![](https://habrastorage.org/getpro/habr/upload_files/a9e/5e9/2ea/a9e5e92ead722d20526099969dae695d.jpeg)
Тем самым, когда этот объект будет десериализован на сервере, он увидит, что пользователь теперь является администратором. Так можно обойти авторизацию.
Другой интересный пример. Во фреймворке CodeIgniter в 2015 году была проблема, связанная именно с сериализацией. Объект не подписывался, и у пользователя в куках можно было найти данные, которые он мог изменить, и скомпрометировать сервер.
Хочу отметить также очень интересное ПО — Ysoserial, которое содержит список уязвимых библиотек. Если наше ПО использует одну из них (мы можем даже об этом не предполагать), то так можно сериализовать специальный объект, который бы потом десериализовался и в итоге стриггерил уязвимость в сериализованной библиотеке. А это привело бы к выполнению кода в нашем приложении.
Так же хотелось бы обратить на специальные функции (или магические методы), которые вызываются при десериализации объектов и могут быть переопределены в коде. Они будут постоянно вызываться, если внутри содержится какая-нибудь внутренняя функция типа «выполнить консольную команду». И тут уже могут быть проблемы.
![](https://habrastorage.org/getpro/habr/upload_files/66f/519/24b/66f51924bdc1c011c5a6ab09a5844d79.jpeg)
Если интересна эта тема, рекомендую еще большую подборку ссылок Алексея Тюрина на GitHub, связанных с данной темой. Наверное, это один из самых исчерпывающих репозиториев по данной теме.
Исправление и предотвращение
Как и всегда, если источник недоверенный, то лучше всегда проверять, какие данные из него приходят.
Есть еще несколько подходов, которые помогут избежать проблем. В Whitelist мы можем определить все классы, которые могут быть десериализованы. Подход довольно действенный и можно сказать — правильный. С помощью BlackList мы можем определить список классов, которые нельзя десериализовать. Но все же его возможно обойти.
Уязвимости авторизации и аутентификации
Начнем пожалуй с самой простой и наглядной для понимания уязвимости - Insecure Direct Object References (IDOR, небезопасные прямые ссылки на объекты) возникает, когда приложение предоставляет прямой доступ к объектам, основываясь на пользовательском вводе, при этом не проверяя права доступа пользователя к данному объекту. Ну или делая это некорректно.
Это позволяет злоумышленнику, изменив значение параметра, используемого для ссылки на объект, обойти авторизацию и получить доступ к ресурсам системы. Это прямой результат того, что приложение принимает пользовательский ввод, чтобы вернуть объект, не проводя при этом проверки авторизации.
Небольшой пример
GET /mail/message/162974011515471087
В данном примере приложение подгружает письма основываясь на идентификаторе в URL. Для проверки можно указать идентификатор письма другого пользователя.
PUT /api/v1/profile/me
Предположим, что такой запрос отправляется при изменении настроек собственного профиля. Для проверки стоит изменить значение «me» на идентификатор другого пользователя.
POST /changepassword.do?user=ivanpetrov
Значение параметра «user» сообщает веб-приложению, какому пользователю необходимо изменить пароль. Здесь для проверки можно подставить другое имя пользователя и изменить его пароль.
В случае, если идентификаторы не инкрементальные, можно быстро проверить большой диапазон значений с помощью инструмента Intruder в Burp Suite. Для этого указываем тестируемый параметр для перебора, диапазон поиска и шаг. После выбора типа данных (например Numbers), анализируем результаты по размеру ответа и/или статусу HTTP:
![](https://habrastorage.org/getpro/habr/upload_files/0c1/f56/48e/0c1f5648e531721d17aaabd512c579dc.jpg)
Тестирование
При тестировании необходимо для каждой роли, каждой функции и запроса, которые выполняются после аутентификации, проверить:
Возможен ли доступ к ресурсу, даже если пользователь не аутентифицирован?
Возможен ли доступ к ресурсу после выхода из системы?
Возможно ли получение доступ к функциям и ресурсам, которые должны быть доступны лишь пользователю с другими правами?
Далее нужно попробовать получить права администратора и протестировать административные функции:
Возможно ли получить доступ к административным функциям через пользователя со стандартными привилегиями?
Возможно ли использовать эти функции через пользователя с другой ролью?
Исправление и предотвращение
Основная проблема, которая приводит к возникновению уязвимости — отсутствующие или недостаточные проверки прав доступа.
Поэтому нужно настроить контроль прав доступа так, чтобы пользователи не могли совершать действия за пределами своих прав и могли получить доступ только к данным, которые им необходимы.
Нельзя полагаться на сложность идентификаторов, например UUID4. Сложность «угадывания» не может являться проверкой авторизации и не гарантирует, что идентификаторы не раскрываются в какой-либо другой функциональности веб-приложения.
Ошибки в механизмах аутентификации
Рассмотрим несколько тривиальных примеров недостатков механизмов аутентификации, которые встречаются вообще повсеместно, но в основном присутствуют на сайтах.
Предположим, есть некоторая форма аутентификации на сайте. Одна из самых распространенных проблем — использование слабых паролей или учетных записей по умолчанию. А если проанализировать какой-либо сервис, то можно встретить даже административную панель.
Например, если сервис использует Application Service Tomcat, то в некоторых случаях можно зайти на порт 8080 и увидеть панель менеджмента Tomcat и дальше попробовать использовать какую-то учетную запись по умолчанию — например, tomcat/tomcat. Иногда она даже подходит, что дает возможность выполнить произвольный код и скомпрометировать сервис.
С другой стороны, в интернете это распространено крайне редко, потому что огромное количество злоумышленников 24*7 сканируют все диапазоны IP-адресов, ища таких сервисов и подбирая доступ.
Рассмотрим задачу, в которой есть форма аутентификации. Предлагается определить, какие пользователи вообще существуют в системе, и попробовать найти несколько таких учетных записей по умолчанию – логичных, которые присутствовали бы в системе (admin, root и т.п.).
Предположим, у нас есть панель роутера (какого — неизвестно). Какие имена учетных записей по умолчанию можно протестировать, чтобы аутентифицироваться? Явно есть admin, root. Возможно, router, user, или guest. Также можно использовать марки популярных роутеров – Cisco, Huawei и т.п. Иногда встречаются даже забавные root root. И даже если всё давно, изменено, guest может остаться открытым, без пароля.
Используем сообщения об ошибках
При подборе логина/пароля злоумышленник может узнать кое-что полезное для себя даже из сообщений системы об ошибке. Например, по сообщению «User not found» можно понять, существует в системе такой пользователь или нет.
Второй вариант, когда система выдает сообщение, что пароль — неверный пароль, и в результате злоумышленник точно знает, что пользователь в системе существует, просто пароль неверный. Это одна из самых частых проблем, связанных с аутентификацией и одна из типичнейших ошибок.
Часто используемые пароли
Существуют миллионы статей про самые часто используемые пароли. Естественно, многие эти пароли довольно слабые и давно скомпрометированы, но даже их использование можно довольно часто.
Кстати, перед тем, как пробовать те или иные пароли, злоумышленник может посмотреть форму регистрацию на сайте и выяснить, с каким паролем можно зарегистрироваться. И сразу уменьшить количество подборов, выкинув те пароли, с которыми зарегистрироваться нельзя.
Так же частенько некоторые пользователи используют пароли, которые как-то связаны с сервисом, компанией, датами и «другими памятными вещами». Возможно кто-то из сотрудников компании MegaBurgerBank (любые совпадения случайны) мог бы использовать пароль для своей учетной записи MegaBurgerBank!@#(любые совпадения случайны) или MegaBurgerBank2020 (любые совпадения случайны).
Чтобы этого избежать, неплохо бы иметь под рукой список запрещенных паролей. Или наоборот, можно было бы сгенерировать такой список для осуществления атаки password spraying с помощью различных инструментов — например, этого.
Заключение
Веб-безопасность была, есть и будет актуальной, поскольку все больше и больше сервисов переходят в веб. Мы еще много раз вернемся к этому вопросу.
11-12 октября весь цвет российского фронтенда соберётся на FrontendConf 2021. Впервые за два года офлайн. Расписание, программа и билеты. Присоединяйтесь и до встречи вживую!