Как предотвратить захват сеанса tomcat?

В моем web.xml я определил ограничение пользовательских данных для некоторых ресурсов:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Personal Area</web-resource-name>
        <url-pattern>/personal/*</url-pattern>
    </web-resource-collection>
    <web-resource-collection>
        <web-resource-name>User Area</web-resource-name>
        <url-pattern>/user/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>
  • Когда я загружаю страницу с http, у меня есть мой JSESSIONID ID1 в моем cookie.
  • Когда я перехожу к context/user/sample.faces, Tomcat делает перенаправление 302 на HTTPS. Но мой JSESSIONID по-прежнему ID1.

Я думаю, что это уязвимость? Или это моя ошибка конфигурации?

Проблема, которую я вижу, следующая: во время просмотра HTTP файла с файлом cookie ID1 есть злоумышленник, который слушает мой сетевой трафик. Он "крадет" мой файл cookie ID1. Теперь я переключаюсь на HTTPS, и мой файл cookie все еще ID1. Я вхожу. Затем злоумышленник может забрать мою сессию, потому что он знает мой файл cookie...

Ответы

Ответ 1

Если это последняя версия Tomcat, у вас может не возникнуть проблема. Однако это зависит от проверки SSL-идентификатора, связанного с сеансом. Это доступно с помощью кода, например

String sslId = (String) req.getAttribute("javax.servlet.request.ssl_session");

(Обратите внимание, что ключ атрибута может измениться в будущем на javax.servlet.request.ssl_session _id - как часть спецификации Servlet 3.0).

Я установил сервлет со следующим методом doGet:

protected void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
    HttpSession session = request.getSession(true);
    String sid = session.getId();
    String sslId = (String) request.getAttribute(
                "javax.servlet.request.ssl_session");
    String uri = request.getRequestURI();
    OutputStream out = response.getOutputStream();
    PrintWriter pw = new PrintWriter(out);
    HashMap<String, Object> secrets;
    Object secret = null;
    Object notSecret;
    Date d = new Date();

    notSecret = session.getAttribute("unprotected");
    if (notSecret == null) {
        notSecret = "unprotected: " + d.getTime();
        session.setAttribute("unprotected", notSecret);
    }
    secrets = (HashMap<String, Object>) session.getAttribute("protected");
    if (secrets == null) {
        secrets = new HashMap<String, Object>();
        session.setAttribute("protected", secrets);
    }
    if (sslId != null) {
        if (secrets.containsKey(sslId))
            secret = secrets.get(sslId);
        else {
            secret = "protected: " + d.getTime();
            secrets.put(sslId, secret);
        }
    }
    response.setContentType("text/plain");
    pw.println(MessageFormat.format("URI: {0}", new Object[] { uri }));
    pw.println(MessageFormat.format("SID: {0}", new Object[] { sid }));
    pw.println(MessageFormat.format("SSLID: {0}", new Object[] { sslId }));
    pw.println(MessageFormat.format("Info: {0}", new Object[] { notSecret }));
    pw.println(MessageFormat.format("Secret: {0}", new Object[] { secret }));
    pw.println(MessageFormat.format("Date: {0}", new Object[] { d }));
    pw.close();
}

Затем я вызывал подходящий незащищенный URL-адрес, используя Firefox и расширение HTTP-заголовков, чтобы получить файл cookie сеанса. Это был ответ, отправленный при навигации по

http://localhost:8080/EchoWeb/unprotected

(мой web.xml, как и ваш, только защищает /user/ * и/personal/*):

URI: /EchoWeb/unprotected
SID: 9ACCD06B69CA365EFD8C10816ADD8D71
SSLID: null
Info: unprotected: 1254034761932
Secret: null
Date: 27/09/09 07:59

Далее я попытался получить доступ к защищенному URL

http://localhost:8080/EchoWeb/personal/protected

и, как и ожидалось, я перенаправлен на

https://localhost:8443/EchoWeb/personal/protected

и ответ был

URI: /EchoWeb/personal/protected
SID: 9ACCD06B69CA365EFD8C10816ADD8D71
SSLID: 4abf0d67549489648e7a3cd9292b671ddb9dd844b9dba682ab3f381b462d1ad1
Info: unprotected: 1254034761932
Secret: protected: 1254034791333
Date: 27/09/09 07:59

Обратите внимание, что идентификатор cookie/session идентичен, но теперь у нас есть новый SSLID. Теперь попробуйте обмануть сервер, используя файл cookie сеанса.

Я установил Python script, spoof.py:

import urllib2

url = "https://localhost:8443/EchoWeb/personal/protected"
headers = {
    'Host': 'localhost:8080',
    'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en-gb,en;q=0.5',
    'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
    'Cookie' : 'JSESSIONID=9ACCD06B69CA365EFD8C10816ADD8D71'
}
req = urllib2.Request(url, None, headers)
response = urllib2.urlopen(req)
print response.read()

Теперь вам не нужно знать Python, в частности - я просто пытаюсь отправить HTTP-запрос на (другой) защищенный ресурс с тем же идентификатором сеанса в Cookie. Здесь ответ, когда я дважды запускал spoof script:

C:\temp>spoof
URI: /EchoWeb/personal/protected
SID: 9ACCD06B69CA365EFD8C10816ADD8D71
SSLID: 4abf0eafb4ffa30b6579cf189c402a8411294201e2df94b33a48ae7484f22854
Info: unprotected: 1254034761932
Secret: protected: 1254035119303
Date: 27/09/09 08:05


C:\temp>spoof
URI: /EchoWeb/personal/protected
SID: 9ACCD06B69CA365EFD8C10816ADD8D71
SSLID: 4abf0eb184cb380ce69cce28beb01665724c016903650539d095c671d98f1de3
Info: unprotected: 1254034761932
Secret: protected: 1254035122004
Date: 27/09/09 08:05

Обратите внимание на приведенные выше ответы, что данные сеанса (значение с меткой времени 1254034761932), которое было установлено в первом незащищенном запросе, было отправлено повсюду, поскольку Tomcat использует тот же сеанс, потому что идентификатор сеанса тоже самое. Это, конечно, не безопасно. Обратите внимание, что идентификаторы SSL отличаются друг от друга, и если вы используете их для ввода данных сеанса (например, как показано), вы должны быть в безопасности. Если я обновляю вкладку Firefox, здесь ответ:

URI: /EchoWeb/personal/protected
SID: 9ACCD06B69CA365EFD8C10816ADD8D71
SSLID: 4abf0d67549489648e7a3cd9292b671ddb9dd844b9dba682ab3f381b462d1ad1
Info: unprotected: 1254034761932
Secret: protected: 1254034791333
Date: 27/09/09 08:05

Обратите внимание, что SSLID тот же, что и для более раннего запроса Firefox. Таким образом, сервер может разделить сеансы, используя значение SSL ID. Обратите внимание, в частности, что "защищенные данные" одинаковы для каждого запроса, сделанного из сеанса Firefox, но различаются для каждого из поддельных сеансов, а также отличаются от сеанса Firefox.

Ответ 2

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

В Tomcat есть защита, но совершенно противоположная. Если вы получаете сеанс в защищенной зоне, этот сеанс не переносится в незащищенную область. Tomcat достигает этого, устанавливая "безопасный" флаг в файле cookie, поэтому cookie не отправляется в HTTP-соединения.

Ответ 3

Я предлагаю изменить sessionId, когда вы аутентифицируете сеанс.
Таким образом, старый sessionId становится бесполезным, и захват сеанса невозможен.
Чтобы изменить sessionId в контейнере сервлета:

  • скопировать все атрибуты текущего сеанса в коллекцию temp
  • session.invalidate()
  • session = req.getSession(true)
  • заполните новый сеанс атрибутами из коллекции temp

О SSLID, обратите внимание, что как клиент, так и сервер могут закрыть соединение в любое время. Когда будет закрыто новое SSL-соединение, и будет создан новый SSID. Таким образом, IMO SSLID не является надежным способом отслеживать (или отслеживать) сеансы.