Возможно, вы заметили, что в настоящее время я работаю над принципами безопасности в отношении безопасных веб-приложений. Основная идея заключается в том, чтобы позволить GlassFish предоставлять приложения с высокой степенью защиты. Одной из причин, по которой мой мозг немного болит, является атака Session Hijacking. Он состоит из эксплуатации механизма управления сеансом http, который обычно управляется для токена сеанса.
В нашем случае JSESSIONID. Атака Session Hijacking скомпрометирует токен сеанса, украдя или предсказав действительный токен сеанса, чтобы получить несанкционированный доступ к приложениям, запущенным на вашем GlassFish.
Существуют разные типы возможных атак. Смотрите
страницу OWASP об этом для деталей. Если вы собираетесь обратиться к этой теме, у вас есть разные варианты с точки зрения реализации. Это то, что я собираюсь описать в этом сообщении в блоге.
SSL твой друг
Основным требованием для предотвращения перехвата сеанса является использование https для ваших приложений. Это первичное заверение, что а) вынюхивать сеанс из потока http нелегко и предотвращает простые атаки типа «человек посередине». Для работы в средах с очень высокой степенью защиты требуется установить ssl-сертификаты на GlassFish и запустить все http-слушатели в безопасном режиме.
Где поставить и как настроить JSESSIONID?
Прежде чем вы начнете читать дальше, вы должны знать, что вся тема посвящена спецификации Servlet и контейнерам. Сама спецификация требует, чтобы cookie-файлы отслеживания сеансов были наиболее удобными для любого пользователя, и определяет множество способов их хранения и передачи. Такое поведение нежелательно в средах с высокой степенью защиты. Поэтому первым делом нужно ограничить cookie-файлы отслеживания сеансов до необходимого минимума.
<session-config>
<!--
Specifies General Session Timeout in Minutes
-->
<session-timeout>15</session-timeout>
<cookie-config>
<!--
Specifies whether any session tracking cookies created
by this web application will be marked as HttpOnly
-->
<http-only>true</http-only>
<!--
Specifies whether any session tracking cookies created
by this web application will be marked as secure
-->
<secure>true</secure>
</cookie-config>
<!--
Specifies whether JSESSIONID is added to the URL
-->
<tracking-mode>COOKIE</tracking-mode>
</session-config>
Что также верно, так это то, что спецификация допускает «безопасные» идентификаторы HttpSession. В 7.1.2 говорится, что
Уровень защищенных сокетов, технология шифрования, используемая в протоколе HTTPS, имеет
встроенный механизм, позволяющий однозначно
идентифицировать несколько запросов от клиента
как часть сеанса. Контейнер сервлета может легко использовать эти данные для
определения сеанса.
Насколько мне известно, GlassFish не реализует эти функции до сих пор. Таким образом, вы должны обойти это. Давайте пойдем:
HttpSession ID и SSL Session ID
Даже если вы используете SSL с самыми надежными из доступных сертификатов, не используете перезапись URL-адресов и включили httpOnly и безопасный режим, никто не защитит вас от атак «человек в браузере» или клиента. побочные атаки. Таким образом, все еще есть некоторые возможности собрать идентификатор сеанса и использовать его с другого компьютера. Если вы хотите реализовать здесь некоторую защиту, вам нужна дополнительная логика в приложении, которая связывает идентификатор SSL с идентификатором HttpSession.
SessionIdValve
Наиболее очевидная вещь — просто сделать их обоих равными. Основная идея здесь заключается в том, чтобы взять идентификатор сеанса SSL из запроса и реализовать свой собственный SessionIdValve, который создает экземпляр HttpSession с этим идентификатором. Ян Люэ имеет основной пример того, как этого добиться с помощью GlassFish v2 в своем блоге. Единственное, что нужно сделать, — это не брать IP-адрес клиента, а coyoReq.getAttribute («javax.servlet.request.ssl_session») и помещать его как HttpSession обратно в запрос (см. Подробности в этом обсуждении на форуме ). Если честно, я не смог заставить это работать со GlassFish 3 ( см. Здесь). Не волнуйтесь: мне все равно не нравится это решение, потому что оно просто недостаточно мобильно. Вы очень тесно связываете свою логику с контейнером, в котором запускаете, и поэтому вам следует избегать такого подхода в целом.
Атрибуты сеанса
Что мне больше нравится, так это использование чего-то вроде HijackingPreventionFilter. Это может быть простой @WebFilter, который отображается на любой ресурс, который должен быть защищен
@WebFilter(dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD,
DispatcherType.INCLUDE, DispatcherType.ERROR}, urlPatterns = {"/*"})
По первому запросу он проверяет существующий сеанс и делает
chain.doFilter(request, response);
или проверяет некоторые атрибуты сеанса на соответствие информации в фактическом запросе. Единственным предварительным условием здесь является то, что у вас есть что-то для добавления начальной информации в ваш недавно созданный сеанс. Есть несколько мест, которые вы могли бы придумать. Лучше всего будет ваш логин. По соображениям безопасности вы всегда должны обновлять HttpSession после успешного входа в систему. После этого вы можете предположить, что запрос от клиента аутентифицируется в вашей системе. Просто получите идентификатор сеанса SSL и установите его в качестве атрибута HttpSession.
String cidSize = (String)request.getAttribute("javax.servlet.request.key_size");
String cid = (String)request.getAttribute("javax.servlet.request.ssl_session");
...
session.setAttribute("CLIENT_SSL_ID", cid);
Вы заметили атрибут cidSize? Javax.servlet.request.ssl_session не является официальным поддерживаемым атрибутом сервлета. Гризли устанавливает это, когда веб-контейнер просит установить ВСЕ атрибуты ssl. Поэтому, когда вы просто запрашиваете «javax.servlet.request.ssl_session», веб-контейнер не распознает его как известный атрибут SSL, и ничего не происходит (Null), но когда вы впервые запрашиваете размер ключа, он распознается веб-контейнером. и он просит Grizzly установить все известные атрибуты SSL, включая ssl_session.
Другим хорошим местом может быть слушатель HttpSession. Большая проблема здесь по-прежнему заключается в том, что вы программируете против функций контейнера, которые не позволяют вашему приложению быть переносимым.
Пользовательские переменные заголовка HTTP
Что на самом деле решает проблему, так это то, что если перед GlassFish у вас есть какое-либо сетевое устройство или прокси-сервер, который просто помещает ssl-session-id в качестве пользовательской переменной заголовка для вашего запроса. В этом случае вам даже не нужно заботиться об этом самостоятельно, вы просто меняете код в своем веб-фильтре, чтобы проверить заголовки вашего запроса.
String cidSize = httpRequest.getHeader("HEADER_CLIENT_SSL_ID");
Единственным недостатком здесь является то, что вы в основном теряете возможность локально запустить его без прокси. Поэтому вам нужно установить класс запуска, который добавит ваш фильтр в конфигурацию, если вы находитесь в производственном режиме.
Вывод: безопасность болезненна
Чем выше ваши требования к безопасности, тем болезненнее ваша разработка. Это основное сообщение. У вас нет ни одного переключателя для включения, чтобы защитить ваше приложение, но у вас есть много винтов, которые нужно затянуть, чтобы все было правильно. Этот пост показывает только немного из полной истории. Я хотел бы видеть, что Servlet EG предпринимает некоторые действия, определяющие более базовую безопасность в спецификации.
Также верно то, что никто не должен использовать высоконадежный GlassFish без какого-либо решения Enterprise Access Management (EAM). Обычно они решают описанные проблемы с помощью своих собственных плагинов и токенов. В любом случае, существуют небольшие установки, которые страдают от очень незначительных возможностей современных серверов Java EE.
Комментарии и предложения? Я хотел бы прочитать их!
От http://blog.eisele.net/2011/06/binding-ssl-sessions-to-httpsessions-in.html