Привет, меня зовут Дмитрий Крылатков, работаю QA-инженером в компании Doubletapp. Я всегда был заинтересован темой тестирования на безопасность, участвую в bug-bounty программах, а также поднимаю осведомленность о существующих уязвимостях среди команд тестирования и разработки. В статье расскажу, как QA может сэкономить ресурсы компании и обеспечить проверку на наличие основных уязвимостей, на что обращать внимание и почему это полезно как для специалиста, так и для бизнеса.

Команда тестирования в первую очередь фокусируется на функциональных требованиях к продукту — проверяет, работает ли приложение в соответствии с ожидаемым результатом, проводит регрессы, разбирается с требованиями и макетами — в общем, обеспечивает качество продукта на всех стадиях. При этом почти всегда QA не отвечает за безопасность продукта — для этого требуются отдельно обученные специалисты, которые умеют искать уязвимости, и как правило этот вид тестирования опускается. Почему это может быть плохо?

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

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

Наша задача как QA — не допустить подобных издержек и повысить качество продукта дополнительными проверками на безопасность. В случае, если мы сможем находить проблемы безопасности еще на этапе функционального тестирования, это сократит часы безопасников и предотвратит траты на bug-bounty программу, если всё это есть в компании. 

Если же отдела безопасности нет, навыки QA в тестировании на безопасность сделают из вас особенно ценного специалиста.

Что такое уязвимость и чем она отличается от бага?

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

Еще одним различием является то, что баги всегда уникальны. Если мы нашли баг в одном приложении, то это абсолютно не гарантирует того, что подобные шаги воспроизведения вызовут аналогичный баг в другом приложении — это, опять же, рождается из уникальных требований к продукту и способов реализации. Уязвимости в этом плане более классифицированы (см. CWE) и, зная паттерн поиска конкретной уязвимости, мы можем её искать в абсолютно любом веб-приложении без опоры на требования к продукту. Ярким примером будут всем известные XSS, которые могут быть найдены силами QA в рамках функционального тестирования мест приложения с пользовательским вводом.

Уязвимости: начнем с простого

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

Список уязвимостей:

  • Clickjacking

  • XSS

  • CSRF

  • IDOR

  • Open Redirect

В качестве технического примера из этого списка я выбрал IDOR-уязвимость. Расшифровывается IDOR как Insecure Direct Object Reference, то есть небезопасная прямая ссылка на объект. IDOR-уязвимость — одна из наиболее часто встречающихся проблем авторизации, которая может привести к критичным проблемам безопасности. Возникают IDOR’ы, когда приложение обеспечивает доступ к объектам на основе введенных пользователем данных. Я уверен, что в компетенциях QA делать проверки этой уязвимости, поэтому давайте разберемся подробнее, как её искать.

В первых двух буквах названия кроется то, на что мы обращаем внимание при тестировании на IDORы, это ID — любые идентификаторы объектов в HTTP-запросах.

Самый простой пример — это когда у пользователя в браузере отображается ссылка по типу https://example.com/profile/passport/123, где последние три цифры являются ID паспорта пользователя. В случае, если пользователь захочет поменять ID паспорта, к примеру, на 122 и на это действие не будет никакой валидации, он откроет страничку чужого паспорта и получит информацию, которая в рамках его сессии ему доступна быть не должна. 

Что мы вообще можем получить с помощью манипуляций с ID? В первую очередь, это неавторизованный доступ к чувствительной информации. По аналогии с предыдущим примером с паспортом, с помощью ID мы можем доставать разные объекты, принадлежащие другим пользователям, — и это далеко не обязательно должна быть личная информация и паспортные данные. Любая информация, которая не должна быть доступна в рамках определенной сессии и роли в приложении, уже будет считаться валидной находкой, если мы смогли её найти через манипуляцию с ID какого-либо чужого объекта. 

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

Во-вторых, получив неавторизованный доступ к объекту, мы можем модифицировать чужие данные. Примером будет служить запрос по типу DELETE /api/users/550123, который потенциально может удалить всех пользователей в приложении.

Кроме этого, могут быть примеры повышения привилегий, если мы сможем атаковать роль выше по иерархии, получая доступ к запросам и объектам, к примеру, юзера-админа. С помощью раскрытия информации мы можем и дальше повышать импакт IDOR-уязвимости, если, например, с помощью неё раскроется объект, в котором хранятся какие-то логины и пароли. На своей практике я находил IDOR, который привел к захвату нескольких аккаунтов.

В-третьих, мы можем рассмотреть прямой доступ к файлам. Файлы также уязвимы к IDOR’ам, поэтому если вы встретите URL по типу https://example.com/passports/123.pdf, очень важно проверить, что «паспорта» с другими ID мы не можем видеть и доставать, — только если это не предусмотрено правами текущей сессии. 

Говоря о методологии поиска, мы можем выделить несколько подходов. Прежде всего, это, конечно, изменение самих ID в запросе. Не забываем, что идентификатор может присуждаться любому объекту, поэтому всегда проверяем весь запрос, включая его тело, а не только query-параметры. Запрос может содержать несколько ID в разных комбинациях — пробуем менять любые из них и смотреть, как на это отреагирует бэкенд. Безусловно, прежде чем это делать, нужно разобраться с логикой авторизации приложения — что эти объекты возвращают и в какой роли.

Можно выделить разные манипуляции с параметрами. Один из методов атак — HTTP Parameter Pollution, который заключается в разделении одинакового параметра на несколько сущностей. В примере ниже через амперсанд мы подставили еще один параметр user_id и записали в него ID другого пользователя.

GET /api_v1/messages?user_id=YOUR_USER_ID&user_id=ANOTHER_USER_ID 

Аналогичным образом мы можем попробовать поменять query-параметры местами
GET /api_v1/messages?user_id=ANOTHER_USER_ID&user_id=YOUR_USER_ID

IDOR’ы могут встречаться в массивах — например когда принимается массив идентификаторов, объекты которых доступны определенным пользователям.

{“users”: [“user_1”: “123”, “user_2”: “124”]}

В данном случае мы можем «расширить» массив, подставив ID пользователя, которого не должно быть в рамках логики авторизации, или использовать привычные методы изменения ID в уже существующих параметрах

{“users”: [“user_1”: “123”, “user_2”: “124”, “user_5”: ”999”]}

Редко, но метко могут выстреливать и не ID вовсе. В запросе ниже идентификация объекта происходит по юзернейму, который, однако, не выглядит привычным. Для примера, юзернейм здесь закодирован в base64 и после декодинга мы можем увидеть юзернейм super_admin.

GET/user/c3VwZXJfYWRtaW4=/data → GET /user/super_admin/data

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

Ещё пара моментов, которые не стоит забывать. В случае, если вы тестируете запрос на IDOR-уязвимость и вам возвращается 403 или же неправомерные действия корректно валидируются другими ошибками, попробуйте добавить слэш GET private/users/89934 → GET private/users/89934/. Возможно, именно этот слэш позволит вам выбраться из регулярки, по которой происходит валидация, и вернёт вам чужой объект. Пробуйте также использовать URL-encoded символы, такие как %20: GET private/users/89934%20.

Мы можем проверять доступ к запросам и объектам через смену авторизации запроса. В случае, если у нас есть несколько ролей, мы можем сделать запрос за админа (или любую роль выше), поменять сессию на роль ниже и сделать запрос. Если нам вернулось то же самое, что было при запросе от админа и стали доступны его объекты, это равнозначно IDOR’у, т.к. является частью недостаточной авторизации объектов. 

Несколько завершающих советов по данной уязвимости:

  • Не нужно репортить неугадываемые ID, если вы не смогли доказать, как их можно найти посредством других запросов в API. Брутфорсить UUID или GUID практически бесполезно.

  • Стоит разобраться, что в приложении ожидаемое поведение, а что нет. Некоторые отчеты на Hackerone закрывались как Not Applicable, потому что исследователь заводил некоторые запросы с разными ID как проблему безопасности, хотя это сделано по дизайну. Например, GET /users/150150 с возвращением информации о пользователе, хотя эта информация была равнозначна той, что он мог бы прокликать на клиенте. То, что вы достаете с помощью ID, должно быть отлично от того, что вы видите на клиенте и что должно быть по факту защищено. Другой пример — это то, что пользователю на клиентской части может быть доступна функциональность выдачи прав на просмотр тех или иных объектов. Тогда это выполнение ожидаемого результата, а не ошибка безопасности.

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

Я прошелся только по основным методологиям и паттернам поиска. Для более детального закрепления информации об этой и других уязвимостях я рекомендую ресурс portswigger. Здесь очень лаконично и удобно описаны уязвимости, в том числе те, которые я предложил изучить на старте. Читаем описание там, пробуем решать лабы для закрепления (ничего страшного, если не будет получаться, — решения всегда есть в интернете). Информации из этого источника должно быть достаточно, чтобы у вас сложилось понимание, что представляют собой те или иные уязвимости и как мы их можем находить в рамках функционального тестирования.

Одним из лучших источников является чтение публичных отчетов о найденных уязвимостях, например, на Hackerone. Очень помогает понять логику и использованные пейлоады.

Софт для поиска уязвимостей

В качестве софта, конечно, можно использовать привычные Fiddler или Postman, но это будет менее удобно, чем инструмент, специально разработанный для специалистов по безопасности. Я говорю о Burp Suite — отладочной прокси, которая среди QA пользуется меньшей популярностью, хотя и для функционального API-тестирования хорошо подходит. С помощью Burp Suite вы сможете перехватывать трафик, использовать удобный интерфейс для анализа и изменения HTTP-запросов, интерспетить запросы и ответы, брутфорсить, декодировать, запускать встроенные сканеры и многие другие полезности, которые пригодятся при тестировании. 

Также Burp поддерживает различные плагины от комьюнити, которые могут облегчить поиски уязвимостей, например, Authmatrix для тестирования на IDOR’ы. 

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

Где применимы знания о поиске уязвимостей?

Область применения довольно широка. Вы можете:

  • Участвовать в bug-bounty программах для дополнительного заработка. 

  • Участвовать в различных CTF и других конкурсах. 

  • Как у специалиста по безопасности у вас может быть несколько веток развития — можно развиваться, к примеру, в пентесты, тестирование мобильных приложений, DevSecOps.

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

Тестирование на безопасность точно может быть частью компетенций QA: с появлением новых навыков вы сможете делать дополнительные проверки на своих продуктах, покрывать ранее неизвестные кейсы и стараться внедрять новые подходы и практики, что положительно скажется на качестве продукта. Как QA в дальнейшем вы сможете влиять на продукт со стороны лучших security-практик еще на этапе создания/тестирования требований. Ну и конечно, будучи специалистом более широкого формата, вы можете делиться знаниями не только с другими тестировщиками, но и с командой разработки.

Поэтому давайте делать наши продукты не только качественными, но и чуть более безопасными!

Также по теме тестирования смотрите лекции на YouTube-канале Doubletapp:

Комментарии (5)


  1. VVitaly
    28.06.2023 05:44

    "Баг — невыполнение ожидаемого поведения программы; уязвимость — выполнение неожидаемого поведения" - смешно... :-)
    Самый простой пример. Выдача пользователю сообщения об ошибке (не предупреждения или сообщения) - это "ожидаемое" или "не ожидаемое" "поведение программы"? Это "баг" или "уязвимость"?
    С одной стороны "не ожидаемое поведение" (по крайней мере пользователем, он то "ждет другого") - значит "уязвимость", а с другой стороны "ожидаемое" пользователем поведение программы не выполнилось - значит "баг"? :-)


    1. evgeniiworkst
      28.06.2023 05:44

      Тут не для пользователя ожидаемое, а для заложенной функциональности ПО.


      1. VVitaly
        28.06.2023 05:44

        Я извиняюсь, "заложенная функциональность ПО" к ожиданию пользователя от него отношения не имеет? :-)
        С таким подходом "Это у нас не баг, а заложенная функциональность" - самый лучший ответ поставщика ПО! :-)


    1. dmitry_krylatkov Автор
      28.06.2023 05:44

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


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


      Уязвимостью в этом примере может служить, например, отсутствие рейт-лимита на код подтверждения. Зная номер телефона другого пользователя злоумышленник может выполнить неожидаемое поведение программы и аутентифицироваться в аккаунт другого пользователя, например, брутфорсом. Очевидно, что ПО не закладывает это как ожидаемый результат.



      1. VVitaly
        28.06.2023 05:44

        "Вывод пользователю сообщения об ошибке - ожидаемый результат выполнения ПО, это не баг и не уязвимость." - а я то "тупой пользователь" думал что ошибок в ПО быть не должно, а вот "защита от дурака" быть обязана... :-)
        А тут вон оно как... Ошибки "ожидаемый результат выполнения ПО"! :-)
        И "логика ПО" совершенно не обязана предусматривать все возможные действия пользователя! :-)
        Поле "номер телефона" в диалоге ввода не нужно ограничивать цифрами и валидировать пользовательский ввод. Пусть пользователь вводит туда все что хочет! К примеру ранее скопированный пользователем в буфер обмена видео файл!
        Пусть после этого приложение молча упадет! Это же предусмотрено его функциональностью? Замечательное у вас ПО и требования к его функциональности! :-)