Web App Security Testing: Tools

Списки инструментов для тестирования безопасности

Также в этой серии постов: Что тестировать?

Web App Security Testing: Attacks

Что тестировать?

В Web Hacking Incident Database (Web App Security Consortium) можно найти список самых популярных типов атак:

  1. Unknown 31%
  2. Denial of Service 17%
  3. SQL Injection 17%
  4. Cross Site Scripting (XSS) 6,3%
  5. Brute Force 3,4%
  6. Predictable Resource Location 2,7%
  7. Stolen Credentials 2,4%
  8. Unintentional Information Disclousure 2.1%
  9. Banking Trojan 2%
  10. Credential/Session Prediction 1.5%
  11. Cross Site Request Forgery 1.3%
  12. Process Automation <1%
  13. Misconfiguration <1%
  14. Known Vulnerability <1%
  15. Abuse of Functionality <1%
  16. DNS Hijacking <1%
  17. Content Spoofing <1%
  18. Administration Error <1%
  19. OS Commanding <1%
  20. Insufficient Authentication <1%

Другой топ атак согласно Cenzic:

  1. Cross Site Scripting, 37%
  2. SQL Injection, 16%
  3. Path Disclosure, 5%
  4. Denial of Service, 5%
  5. Code Execution, 4%
  6. Memory Corruption, 4%
  7. Cross Site Request Forgery, 4%
  8. Information Disclosure, 3%
  9. Arbitrary File, 3%
  10. Local File Include, 2%
  11. Remote File Include, 1%
  12. Overflow 1%
  13. Other, 15%

И наконец ещё один топ по версии Open Web App Security Project

  1. Injection
  2. XSS
  3. Broken Authentication and Session Management
  4. Insecure Direct Object References
  5. Cross Site Request Forgery (CSRF)
  6. Security Misconfiguration
  7. Insecure Cryptographic Storage
  8. Failure to Restrict URL Access
  9. Insufficient Transport Layer Protection
  10. Unvalidated Redirects and Forwards

Следующий вопрос: Чем тестировать?

Хороший клиент — тонкий клиент

Клиент должен заниматься трансформацией данных, а не её реструктуризацией. Другими словами, тонкий клиент можно представить как монитор, который не решает ЧТО показывать, а решает КАК это показывать. Если сервер возвращает 5 полей, значит все они должны быть трансформированы клиентом. Что бы этого добится, нужна гибкость на стороне сервера.

Структура по запросу

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

/resource?select=a,b,c,d
/resource?select=complex/subfield,b

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

Запрос на полную версию ресурса выдаст эту новую структуру и клиент молчаливо обработает её. Здесь предполагается расширяемость. В уточняющих запросах такой расширяемости не предусматривается.

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

Мета информация

Для обработки структуры, которая может меняться нужен отдельный подход. Смысл в том, чтобы клиент мог обработать любое поле. Для этого нужна мета информация, описывающая типы полей. Типы полей могут быть как примитивными: число, строка, дата; так и комплексными: ссылка.

По сути контракт между сервером и клиентом происходит на уровне типов данных, а не на уровне структур. Контейнером этих типов является media type. Версия клиента это в том числе то, сколько типов он может обработать.

<structure type="BlogFooter">
<property name="ctime" type="date" format="YYYY MM DD">2012 10 05</property>
<property name="author" type="string" >George Mihailov</property>
</structure>

Интернет браузер

Стоит приглядеться к тому как реализована обработка HTML содержимого браузером. Наличие синтаксических ошибок НЕ влияет критически на отображаемое содержимое. Многие не любят HTML из-за его хаоса с тегами, но однако это позволяет интернету быть очень стабильной системой. В отличие от того-же XHTML/XML, в котором синтаксическая ошибка вообще не позволяет обработать документ. Такая особенность браузеров игнорировать не понятные им теги/аттрибуты делают переход на HTML5 незаметным для пользователя, в том смысле что браузер не «валится» в случае чего.

Почему стоит использовать Hypermedia Controls (HATEOAS)

Документация типичного API содержит список ресурсов и шаблоны доступа к ним. Например

/api/user/{id}/friends

Проблема такого подхода состоит в том, что пользователь API узнает этот адрес напрямую из документации 🙂 и по сути хардкодит его в своём приложении. А откуда вообще клиент берёт ID пользователя? Из другого ресурса! Который точно так же захардкоден. Выходит что клиент неявно прописывает у себя связь между пользователем и его друзьями. Что-то сдесь не так.

А что делать когда в сервисе возникают изменения, котоые касаются этого ресурса? Приходится вводить новую версию API. А лучше заранее указать версию API, чтоб не повадно было:

/api/v1/user/{id}/friends

Спрашивается, почему клиент должен неявно делать предположения о местонахождении ресурса? Его ведь интересует не адрес, а связь пользователя с друзьями. Таким образом это стоит переписать в более универсальной форме:

<link rel=»user:friends» href=»/user/1234/friends» />

Теперь о вопросе откуда вообще клиент берёт ID пользователя. Берёт он его из service endpoint, то есть из публичного адреса, который считается входной точкой в наше приложение. Например

/users

возвращающий список пользователей с нашими ссылками.

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

Первый вариант:

  1. Получить список друзей 007 (/users)
  2. Найти ID Васи (process users)
  3. Получить список друзей по ID Васи (/users/{id}/friends)

Второй вариант:

  1. Получить список друзей 007 (/users)
  2. Найти Васю (process users)
  3. Перейти по ссылке user:friends (process user:friends link)

Шагов столько же, только знания для местонахождения ресурса разные. Мало того что в первом варианте нам заранее нужно знать куда идти, так нам ещё обязательно нужно получить идентификатор. Для второго варианта нужно только понимать связь user:friends.

Итого, в чём выйгрышь от использования Hypermedia Controls:

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

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

Resource Version Downgrade: Версионность REST API

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

Известные решения

  1. Добавление версии в начале URI: /api/v0.1/profile
  2. Использование custom media type с параметром: application/vnd.example.com; ver=0.1
  3. Добавление в строку запроса: /api/profile?v=0.1
  4. Использование HTTP заголовка: X-Api-Version: 0.1
    например реализация в OData

Проблема в том, что они вынуждают жёстко синхронизировать версию между клиентом и сервером. Однако версия это скорей серверный артефакт, клиенту важны сами изменения.

Resource version downgrade

Итак, есть ресурс /resource/example. В какой то момент времени происходят изменения в его структуре (значения могут меняться сколько угодно). В ответе помещается ссылка «downgrade», ведущая на предыдущую версию этого ресурса: /resource/example/version/3.

После получения клиентом представления ресурса (/resource/example), может возникнуть ошибка, связанная с неожиданным поведением. В таком случае, клиент может перейти по ссылке «downgrade» и получить более совместимую версию ресурса.

Этот процесс может повторятся сколько угодно в зависимости от кол-ва версий ресурса и удовлетворяющего состояния для клиента. Ссылка «downgrade» после получения /resource/example/version/3 будет уже другой — /resource/example/version/2.

Таким образом ссылка «downgrade» является абстрагированием от абсолютного значения версии. Такое поведение похоже на всем известный способ проходу по списку: prev/next/start/end.

Обратная совместимость

Даже если клиент смог обработать более старую версию ресурса, ссылки внутри представлении ресурса скорей всего устарели. То есть дальнейшее взаимодействие с этим представлением остаётся под вопросом. Решением тут будет подстановка в качестве URI, адреса страницы с предупреждением об устаревшем клиенте. В зависимости от ситуации часть ссылок может продолжать работать, а часть будет вести на эту страницу. Важно сохранить структуру ссылок неизменной.

Таким образом, клиент всегда получит удовлетворительный ответ от сервера, а дальнейшее  взаимодействие будет нейтрализовано. Это наглядный пример ситуации, почему клиент должен полагаться на связи (<link rel=»»>), а не на ссылки.

Хранение данных подходящими средствами

Хорошая презентация на тему NoSQL и Neo4j в частности

Особенно хочется подчеркнуть мысль о подборе хранилища данных исходя из нужд. В этом смысле останавливаться только на RDBS не стоит, так как уже сформировался стек разных систем:
  • Key-value
  • Document
  • Graph
  • BigTable

Две стратегии авторизации на сайте

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

Собственно какие стратегии авторизации имеются? Первая, это когда содержимое ПЕРЕД отдачей клиенту, фильтруется и модифицируется. Например часто в верхней панели выводится имя, если пользователь опознан, либо форма для авторизации в противном случае. Далее, как правило, для неопознанного посетителя скрывается интрфейс и сайт получается закрытым.

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

При такой «открытой» авторизации, действует правило:  пользовательский интерфейс выдаётся как авторизированному посетителю, но исходящие запросы на сервер жёстко фильтруются и при необходимости ведут на страницу аутентификации.

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

Стоит отметить, что закрытая авторизация не отменяет открытой и по сути дублирует её. То есть кроме определения доступа на уровне ресурсов производится фильтрация на уровне интерфейса. Если говорить в терминах MVC, то открытая авторизация производится на уровне контроллера, а закрытая на уровне представления.