RESOLVIDO: Tomcat - Renovar o JSESSIONID utilizando Filter - Evitar ataques de Session Fixation

Galera,

sou novo no fórum, estou com problema meio advanced (pra mim) na minha aplicação. Recentemente ela passou pelo scanner do IBM Rational Scan e a mesma apresentou um problema de session fixation. A ferramenta sugeriu alterar o JSESSIONID após o login.
Depois de meses pesquisando, encontrei uma solução, aparentemente, simples. Bastaria ativar uma classe Valve no Tomcat.

Acontece que eu fiz o que foi solicitado e não aconteceu nada. O JSESSIONID não modifica após o login. Vocês poderiam me ajudar, vou postar o meu arquivo “context.xml” pra vocês.

context.xml

WEB-INF/web.xml

<Valve className="org.apache.catalina.authenticator.BasicAuthenticator" changeSessionIdOnAuthentication="true"/>
<Valve className="org.apache.catalina.authenticator.DigestAuthenticator" changeSessionIdOnAuthentication="true"/>
<Valve className="org.apache.catalina.authenticator.FormAuthenticator" changeSessionIdOnAuthentication="true"/>
<Valve className="org.apache.catalina.authenticator.SSLAuthenticator" changeSessionIdOnAuthentication="true"/>

Link do Tomcat falando sobre a Valve.
http://tomcat.apache.org/tomcat-6.0-doc/config/valve.html#SSL_Authenticator_Valve

Versão do Tomcat: 6.0.35

Preciso incluir alguma coisa no meu JSP para que funcione?

Atenciosamente,
Rafael Franco

A proteção “built-in” do Tomcat vale apenas quando o login é gerenciado pelo container. Seu caso é este ? Se vc. utiliza algum outro framework para autenticação (ex: spring security), então é sua responsabilidade invalidar a sessão no momento de autenticar o usuário, criar uma nova e inserir na nova sessão algum atributo que lhe diga que o usuário está logado.

psevestre, A aplicação que estou falando é o Webrun… Já ouviu falar? (www.softwell.com.br)

O login não é gerenciado pelo “Container”, o problema que eu não tenho o fonte da classe que faz a autenticação do usuário, eu teria que criar uma classe a parte só pra isso.
No final, o que preciso é alterar a JSESSIONID, pq essa ferramenta da IBM acusa isso, e pro cliente isso é a “morte”.

Estou tendo que me virar pra alterar essa JSESSIONID alterando somente os JSP, mas ta “difícil”.

O usuário chega na tela “login.jsp” e após autenticado ele é redirecionado para a “main.jsp”

Li em um site (http://www.koelnerwasser.de/?p=11) que eu deveria copiar a sessão antiga, gerar uma nova, destruir a antiga e copiar os dados da antiga pra essa nova.

Estou tentando fazer isso na página que é chamada após o login (main.jsp).

<%
//Passo 1 - Fazer uma cópia da sessão antiga
Session oldSession; //Até aqui tudo bem rsrs…

oldSession = request.getSession(true); //Dá erro 500

//daqui pra baixo, nada importa… eu não consigo armazenar a sessão antiga.
//Esse request.getSession(true) é um objeto do tipo “HttpSession”

//SavedRequest saved = (SavedRequest) oldSession.getNote(Constants.FORM_REQUEST_NOTE);

//Passo 2 - Invalidar a sessao antiga
//request.getSession(true).invalidate(); //Esse comando funciona, mas eu volto pra tela de login se eu executo ele…
//request.setRequestedSessionId(null);
//request.clearCookies();

// Passo 3 - Criar uma sessao e copiar ela para o request
//Session newSession = request.getSession(true);
//request.setRequestedSessionId(newSession.getId());

// step 4: copy data pointer from the old session
// to the new one
//if (saved != null) {
// newSession.setNote(Constants.FORM_REQUEST_NOTE, saved);
//}
%>

Estou perdidão… não sei mais o que faço…

Rafael Franco

Rafael,

  1. O Session Fixation representa um risco significativo. Se mesmo uma ferramenta automatizada conseguiu pegar o problema pode ter certeza que um hacker vai conseguir fazer isto também. Assim, não está errado o cliente que acha que o problema é a “morte”.

  2. Se o pacote é de 3os, que tal usar o suporte deles. Afinal vc (ou seu empregador) pagou por ele.

  3. Vc. precisa invalidar a sessão e criar a nova no ponto de código em que é feita a autenticação, ou seja, no mesmo ciclo de processamento em que as credenciais são enviadas.

  4. Se a questão é só copiar os objetos de sessão, vc. pode fazer isto em um filtro normal, não precisa ser um Valve.

psevestre,

Estou tentando com o fornecedor da aplicação a solução do problema, mas eles são “lentos”.

Conforme sugerido, vou tentar implementar um filtro pra fazer a mudança do JSESSIONID.
Posto em breve o resultado.

No mais, só tenho a agradecer sua atenção!

Rafael Franco

Prezados,

criei esse filtro para tentar invalidar a sessão, mas estou recebendo esse erro:

type Exception report
description The server encountered an internal error () that prevented it from fulfilling this request.
exception
java.lang.ClassCastException: org.apache.catalina.connector.RequestFacade cannot be cast to org.apache.catalina.connector.Request
aspprev.RenovaSessao.doFilter(RenovaSessao.java:19)

Vocês sabem o que pode ser? A ideia do filtro é alterar o JSESSIONID após o login do usuário.

//CLASSE FILTRO
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.Session;
import org.apache.catalina.authenticator.Constants;
import org.apache.catalina.authenticator.SavedRequest;
import org.apache.catalina.connector.Request;

/**
*

  • @author rfranco
    */
    public class RenovaSessao implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException
    {
    //Fazer um CAST do request
    Request req = (Request)(HttpServletRequest) request;

    // Passo 1 - Salvar a sessao anterior
    Session oldSession = (Session)req.getSession(true);
    SavedRequest saved = (SavedRequest) oldSession.getNote(Constants.FORM_REQUEST_NOTE);

    // Passo 2 - Invalidar a sessao atual
    req.getSession(true).invalidate();
    req.setRequestedSessionId(null);
    req.clearCookies();

    // Passo 3 - Criar uma nova sessao e copiar ela para o request
    Session newSession = (Session) req.getSession(true);
    req.setRequestedSessionId(newSession.getId());

     // Passo 4 - Copiar os dados da antiga sessão e colar na nova
     if (saved != null) {
         newSession.setNote(Constants.FORM_REQUEST_NOTE, saved);
     }
     
     chain.doFilter(request, response);
    

    }

    public void init(FilterConfig config) throws ServletException {}
    public void destroy() {}
    }
    //Fim da Classe

Desde já, agradeço!
Rafael Franco

Rafael,

Não use as classes do catalina. No seu caso basta usar um javax.servlet.http.HttpServletRequest.

Esta é a provável causa do CCE.

Prezados,

voltei para a Valve… Acho que estou bem perto! Não está dando mais erro de compilação rsrsrs…

Criei uma classe Valve, alterei meu “server.xml”, mas, aparentemente, minha Valve não está sendo acionada, pois tem alguns registros de “Log” dentro dela e não tá saindo nada no log do Tomcat.

Como faço pra sair os logs???
Será que não está saindo log pq ela não está sendo acionada?

Minha Classe, baseada nessa fonte esta abaixo… (http://marvinsmutterings.blogspot.com.br/2010/02/fixing-session-fixation-in-liferay-on.html)

===========
package aspprev.valves;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.ServletException;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.apache.juli.logging.Log;

public class FixSessionFixationValve extends ValveBase {

private static final String INFO = "be.belgacom.enable.security.FixSessionFixationValve/1.0";

private String parameterName = null;
private String value = null;

@Override
public String getInfo() {
    return INFO;
}

public String getParameterName() {
    return parameterName;
}

public void setParameterName(String parameterName) {
    this.parameterName = parameterName;
}

public String getValue() {
    return value;
}

public void setValue(String value) {
    this.value = value;
}

@SuppressWarnings("unchecked")
public void invoke(Request request, Response response) throws IOException, ServletException {
    String param = request.getParameter(getParameterName());
    if (param != null && getValue().equals(param)) {
        
        Log logger = container.getLogger();
   
        // Save old session
        Session oldSession = request.getSessionInternal(true);            
        Map&lt;String, Object&gt; oldAttribs = new HashMap&lt;String, Object&gt;();
        Map&lt;String, Object&gt; oldNotes =  new HashMap&lt;String, Object&gt;();

    if (logger.isDebugEnabled()) 
        logger.debug(&quot;Old session ID: &quot; + oldSession.getId());
  
        // Save HTTP session data
        Enumeration names = oldSession.getSession().getAttributeNames();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            oldAttribs.put(name, oldSession.getSession().getAttribute(name));
        }

        // Save Tomcat internal session data
        Iterator it = oldSession.getNoteNames();
        while (it.hasNext()) {
            String name = (String) it.next();
            oldNotes.put(name, oldSession.getNote(name));
        }

        // Invalidate old session
        request.getSession(true).invalidate();
        request.setRequestedSessionId(null);
        request.clearCookies();

        // Create a new session and set it to the request
        Session newSession = request.getSessionInternal(true);
        request.setRequestedSessionId(newSession.getId());
  
        if (logger.isDebugEnabled()) 
            logger.debug(&quot;New session ID: &quot; + newSession.getId());
  
        // Copy data pointer from the old session to the new one. Restore HTTP session data
        for (String name : oldAttribs.keySet()) {
            newSession.getSession().setAttribute(name, oldAttribs.get(name));
        }
     
        // Restore Tomcat internal session data
        for (String name : oldNotes.keySet()) {
            newSession.setNote(name, oldNotes.get(name));
        }            
    }     
    getNext().invoke(request, response);
}

}

Meu Arquivo "server.xml"

<?xml version=‘1.0’ encoding=‘utf-8’?>
<Server port=“8005” shutdown=“SHUTDOWN”>

&lt;Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /&gt;
&lt;Listener className="org.apache.catalina.core.JasperListener" /&gt;
&lt;Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /&gt;
&lt;Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" /&gt;
&lt;Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /&gt;

&lt;GlobalNamingResources&gt;
	&lt;Resource name="UserDatabase" auth="Container"
          type="org.apache.catalina.UserDatabase"
          description="User database that can be updated and saved"
          factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
          pathname="conf/tomcat-users.xml" /&gt;
&lt;/GlobalNamingResources&gt;

&lt;Service name="Catalina"&gt;
	&lt;Connector port="8081" protocol="HTTP/1.1" redirectPort="8081" enableLookups="false" server="Undefined" /&gt;			   		   
	&lt;Connector port="8009" protocol="AJP/1.3" redirectPort="443" /&gt;

&lt;Engine name="Catalina" defaultHost="localhost"&gt;
	&lt;Valve className="org.apache.catalina.valves.RequestDumperValve"/&gt;      	 	 
	&lt;Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/&gt;
	
	&lt;Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false"&gt;
		&lt;Valve className="aspprev.valves.FixSessionFixationValve" /&gt;
		&lt;Valve 	className="org.apache.catalina.valves.AccessLogValve" prefix="AccessLogValve." suffix=".txt" pattern="common" resolveHosts="false"/&gt;       
	&lt;/Host&gt;
&lt;/Engine&gt;

</Service>
</Server>

Meu arquivo Context.xml

<?xml version=‘1.0’ encoding=‘utf-8’?>
<Context useHttpOnly=“true”>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
</Context>

Bom… conseguindo fazer aparecer os “logs” já é um grande passo, pois aí eu vou conseguir debugar e ver o que está acontecendo…

Desde já, agradeço a atenção de todos.
Rafael Franco

Ainda acho que a Valve não é o caminho, até pelo fato de que vc. não tem como especificar em quais URIs ela entra ou não. Acho que vc. não quer ficar copiando a sessão a cada acesso. Com um filtro vc. mapeia apenas as URIs interessantes.

Voltado ao seu problema, verifique se o valve está sendo acionado colocando um breakpoint em uma página JSP qualquer e inspecionando o stack. Se sua Valve não aparecer, significa que vc. não configurou corretamente o contexto web.

Philippe.

Philippe,

segui o seu conselho e não utilizei Valve, utilizei Filter mesmo.

FUNCIONOU!!! VALEW!!!

Segue o código fonte do filtro para futuras consultas de usuários do fórum…

=========================
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;

public class RenovaSessao implements Filter {

private ServletContext context = null; 

public void init(FilterConfig config) throws ServletException {
    //Obter o contexto para gravação de logs
    this.context = config.getServletContext();
}

public void destroy() {}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{               
    //context.log("ENTROU NO FILTRO...");                       
    
    if (request instanceof HttpServletRequest){                                              
        //Receber o request            
        HttpServletRequestWrapper req = new HttpServletRequestWrapper((HttpServletRequest) request); 
        
        //Gravar logs
        //context.log("ID Anterior: " + (String) req.getSession().getId());
        
        if (req.getSession() != null && !req.getSession().isNew()) {                
            //context.log("PASSO 1 - Recuperar a sessão atual");
            // Recuperar a sessão atual
            HttpSession oldSession = (HttpSession) req.getSession();            
            
            //context.log("PASSO 2 - Salvar atributos da sessão atual");
            // Salvar os atributos da sessão atual
            Map<String, Object> oldAttribs = new HashMap<String, Object>();
            Enumeration names = oldSession.getAttributeNames();
            while (names.hasMoreElements()) {
                String name = (String) names.nextElement();
                oldAttribs.put(name, oldSession.getAttribute(name));
            }
            
            //context.log("PASSO 3 - Invalidar a sessão atual");
            // Invalidar a sessão atual
            req.getSession().invalidate();
            
            //context.log("PASSO 4 - Criar uma nova sessão");
            // Criar uma nova sessão
            HttpSession newSession = (HttpSession) req.getSession(true);
            //context.log("Nova Sessão: " + newSession.getId());
            
            //context.log("PASSO 5 - Copiar os dados da sessão antiga para a nova sessão");
            // Copiar os dados da sessão antiga para a nova sessão. 
            for (String name : oldAttribs.keySet()) {
                newSession.setAttribute(name, oldAttribs.get(name));
                context.log("Atributo: " + name + oldAttribs.get(name) + " Valor: "+oldAttribs.get(name));
            }
            
            //Salvar o novo request
            //context.log("PASSO 6 - Salvar o novo request...");
            req.setRequest(request);
        }
    }
    
    //context.log("SAIU DO FILTRO...");        
    chain.doFilter(request, response);       
}

}

Configuração do arquivo web.xml

=========================

<filter>
	<display-name>renovaSessao</display-name>
	<filter-name>renovaSessao</filter-name>
	<filter-class>aspprev.RenovaSessao</filter-class>
</filter>

<!-- URLs onde o filtro deve ser acionado -->

<filter-mapping>
	<filter-name>renovaSessao</filter-name>
	<url-pattern>/logon.do</url-pattern>
</filter-mapping>  

=========================

Atenciosamente,
Rafael Franco