dimanche 19 mai 2013

XSSValve

Alors que les sites web mettent en œuvre des requêtes AJAX pour ajouter du dynamisme à leur page, il devient parfois difficile d'assurer l'intercommunication des différents modules d'un système d'informations. Si chaque application ou "site" doit échanger des données via des requêtes AJAX, il est fort à parier que tôt ou tard, un message de sécurité vienne bloquer le bon fonctionnement du système.
Plusieurs techniques de contournement existent, plus compliqués les unes que les autres. Face à de telles difficultés, il est parfois nécessaire de revenir à la source et de regarder de plus près les mécanismes prévus dans les spécifications et normes qui définissent ces technologies.

Globalement, la règle a retenir est la suivante :
Les pages de deux sites différents peuvent communiquer si elles appartiennent au même domaine.

Comment donc définir le domaine d'une page ?
Assez simplement en positionnement l'attribut domain de l'objet document.*

Au chargement d'une page, l’exécution de la commande suivante positionne le domaine d'une page :
    document.domain = <my_domain>;

Même si théoriquement définir cet attribut ne présente aucune difficulté, la mise en œuvre peut s'avérer nettement plus compliqué. En effet, il faut appliquer cet instruction dans toutes les pages devants communiquées. Sur certain site, cela peut représenter de nombreuses pages.


Le code qui suit constitue une valve qui insert dans chaque page renvoyée par un moteur de servlets ou serveur d'applications l'instruction décrite ci-dessus.

Trois classes définissent cette valve :
  • XssDomainInjectorValve
Cette classe contient la "logique métier" de la valve. Lors de son premier appel, elle s'initialise en calculant le domaine dans lequel est inscrit le serveur. Ensuite, elle recherche le tag <html> de la page renvoyée et insert l'instruction document.domain, ainsi que la valeur précédemment calculée.


import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.connector.ResponseWrapper;
import org.apache.catalina.valves.ValveBase;

public class XssDomainInjectorValve extends ValveBase {

    private static String HEAD_TAG = "</head>";
    private static String domainJS = null;
    
    private void detectDomain(Request req) {
        String[] n = req.getHost().getName().split("\\.");
        int len = n.length;
        if (len>2) {
            domainJS = "<script>document.domain='"+n[len-2]+"."+n[len-1]+"'</script>";
        }
    }
    
    @Override
    public void invoke(Request req, Response resp) throws IOException,
            ServletException {

        if (domainJS==null) {
            detectDomain(req);
        }

        if (domainJS!=null) {
            ResponseWrapper wrapper = new ResponseWrapper(resp);
            wrapper.setResponseFacade(new CharResponseFacadeWrapper(wrapper));
                    
            getNext().invoke(req, wrapper);
            
            String content = wrapper.getResponse().toString();

            if (wrapper.getContentType()!=null && 
                wrapper.getContentType().indexOf("html")>=0 &&
                content.indexOf(HEAD_TAG)>0 ) {

                CharArrayWriter caw = new CharArrayWriter();
                caw.write(content.substring(0, content.indexOf(HEAD_TAG)-1));
                caw.write(domainJS);
                caw.write(content.substring(content.indexOf(HEAD_TAG)-1, content.length()));
                resp.setContentLength(caw.toString().length());
                PrintWriter out = resp.getWriter();
                out.write(caw.toString());
                out.close();
                caw.close();
            }
        } else {
            getNext().invoke(req, resp);            
        }
    }
}

  • ResponseWrapper
Parce qu'il n'est pas possible d'accéder (et donc de modifier) le contenu d'une page renvoyé par le serveur, cette classe permet de détourner la réponse du serveur vers la classe CharResponseFacadeWrapper.

import java.io.CharArrayWriter;
import java.io.PrintWriter;

import org.apache.catalina.connector.Response;
import org.apache.catalina.connector.ResponseFacade;

public class CharResponseFacadeWrapper extends ResponseFacade {

    private CharArrayWriter output;
    
    protected CharResponseFacadeWrapper(Response resp) {
        super(resp);
        output = new CharArrayWriter();
    }
    
    public PrintWriter getWriter() {
        return new PrintWriter(output);
    }
    
    public String toString() {
        return output.toString();
    }
}

  • CharResponseFacadeWrapper
Cette classe permet d'accéder au contenu de la réponse renvoyé par le serveur (et donc de la modifier).


import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Locale;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.connector.Response;

public class ResponseWrapper extends Response {

    public Response response = null;

    public ResponseFacade respFacade = null;
   
    public ResponseWrapper(Response resp) {
        super();
        if (resp == null) {
            throw new IllegalArgumentException("Response cannot be null");
        }
        this.response = resp;
    }
       
    public void setResponseFacade(ResponseFacade rf) {
        //this.response.facade = rf;
        this.respFacade = rf;
    }
   
    @Override
    public HttpServletResponse getResponse() {
        //return this.response.getResponse();
        return this.respFacade;
    }

   
   
    @Override
    public void addCookie(Cookie cookie) {
        this.response.addCookie(cookie);
    }

    @Override
    public void addCookieInternal(Cookie cookie) {
        this.response.addCookieInternal(cookie);
    }

    @Override
    public void addDateHeader(String name, long value) {
        this.response.addDateHeader(name, value);
    }

    @Override
    public void addHeader(String name, String value) {
        this.response.addHeader(name, value);
    }

    @Override
    public void addIntHeader(String name, int value) {
        this.response.addIntHeader(name, value);
    }

    @Override
    public void clearEncoders() {
        this.response.clearEncoders();
    }

    @Override
    public boolean containsHeader(String name) {
        return this.response.containsHeader(name);
    }

    @Override
    public ServletOutputStream createOutputStream() throws IOException {
        return this.response.createOutputStream();
    }

    @Override
    public String encodeRedirectURL(String url) {
        return this.response.encodeRedirectURL(url);
    }

    @Override
    @Deprecated
    public String encodeRedirectUrl(String url) {
        return this.response.encodeRedirectUrl(url);
    }

    @Override
    public String encodeURL(String url) {
        return this.response.encodeURL(url);
    }

    @Override
    @Deprecated
    public String encodeUrl(String url) {
        return this.response.encodeUrl(url);
    }

    @Override
    public void finishResponse() throws IOException {
        this.response.finishResponse();
    }

    @Override
    public void flushBuffer() throws IOException {
        this.response.flushBuffer();
    }

    @Override
    public int getBufferSize() {
        return this.response.getBufferSize();
    }

    @Override
    public String getCharacterEncoding() {
        return this.response.getCharacterEncoding();
    }

    @Override
    public Connector getConnector() {
        return this.response.getConnector();
    }

    @Override
    public int getContentCount() {
        return this.response.getContentCount();
    }

    @Override
    public long getContentCountLong() {
        return this.response.getContentCountLong();
    }

    @Override
    public int getContentLength() {
        return this.response.getContentLength();
    }

    @Override
    public String getContentType() {
        return this.response.getContentType();
    }

    @Override
    public Cookie[] getCookies() {
        return this.response.getCookies();
    }

    @Override
    public org.apache.coyote.Response getCoyoteResponse() {
        return this.response.getCoyoteResponse();
    }

    @Override
    public String getHeader(String name) {
        return this.response.getHeader(name);
    }

    @Override
    public Collection<String> getHeaderNames() {
        return this.response.getHeaderNames();
    }

    @Override
    public String[] getHeaderNamesArray() {
        return this.response.getHeaderNamesArray();
    }

    @Override
    public String[] getHeaderValues(String name) {
        return this.response.getHeaderValues(name);
    }

    @Override
    public Collection<String> getHeaders(String name) {
        return this.response.getHeaders(name);
    }

    @Override
    public boolean getIncluded() {
        return this.response.getIncluded();
    }

    @Override
    public String getInfo() {
        return this.response.getInfo();
    }

    @Override
    public Locale getLocale() {
        return this.response.getLocale();
    }

    @Override
    public String getMessage() {
        return this.response.getMessage();
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return this.response.getOutputStream();
    }

    @Override
    public PrintWriter getReporter() throws IOException {
        return this.response.getReporter();
    }

    @Override
    public Request getRequest() {
        return this.response.getRequest();
    }
/*
    @Override
    public HttpServletResponse getResponse() {
        return this.response.getResponse();
    }
*/
    @Override
    public int getStatus() {
        return this.response.getStatus();
    }

    @Override
    public OutputStream getStream() {
        return this.response.getStream();
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return this.response.getWriter();
    }

    @Override
    public boolean isAppCommitted() {
        return this.response.isAppCommitted();
    }

    @Override
    public boolean isClosed() {
        return this.response.isClosed();
    }

    @Override
    public boolean isCommitted() {
        return this.response.isCommitted();
    }
/*
    @Override
    protected boolean isEncodeable(String location) {
        return this.response.isEncodeable(location);
    }
*/
    @Override
    public boolean isError() {
        return this.response.isError();
    }

    @Override
    public boolean isSuspended() {
        return this.response.isSuspended();
    }

    @Override
    public boolean isWriteable() {
        return this.response.isWriteable();
    }

    @Override
    public void recycle() {
        this.response.recycle();
    }

    @Override
    public void reset() {
        this.response.reset();
    }

    @Override
    public void reset(int status, String message) {
        this.response.reset(status, message);
    }

    @Override
    public void resetBuffer() {
        this.response.resetBuffer();
    }

    @Override
    public void resetBuffer(boolean resetWriterStreamFlags) {
        this.response.resetBuffer(resetWriterStreamFlags);
    }

    @Override
    public void sendAcknowledgement() throws IOException {
        this.response.sendAcknowledgement();
    }

    @Override
    public void sendError(int status, String message) throws IOException {
        this.response.sendError(status, message);
    }

    @Override
    public void sendError(int status) throws IOException {
        this.response.sendError(status);
    }

    @Override
    public void sendRedirect(String arg0) throws IOException {
        this.response.sendRedirect(arg0);
    }

    @Override
    public void setAppCommitted(boolean appCommitted) {
        this.response.setAppCommitted(appCommitted);
    }

    @Override
    public void setBufferSize(int size) {
        this.response.setBufferSize(size);
    }

    @Override
    public void setCharacterEncoding(String charset) {
        this.response.setCharacterEncoding(charset);
    }

    @Override
    public void setConnector(Connector connector) {
        this.response.setConnector(connector);
    }

    @Override
    public void setContentLength(int length) {
        this.response.setContentLength(length);
    }

    @Override
    public void setContentType(String arg0) {
        this.response.setContentType(arg0);
    }

    @Override
    public void setCoyoteResponse(org.apache.coyote.Response coyoteResponse) {
        this.response.setCoyoteResponse(coyoteResponse);
    }

    @Override
    public void setDateHeader(String name, long value) {
        this.response.setDateHeader(name, value);
    }

    @Override
    public void setError() {
        this.response.setError();
    }

    @Override
    public void setHeader(String name, String value) {
        this.response.setHeader(name, value);
    }

    @Override
    public void setIncluded(boolean included) {
        this.response.setIncluded(included);
    }

    @Override
    public void setIntHeader(String name, int value) {
        this.response.setIntHeader(name, value);
    }

    @Override
    public void setLocale(Locale locale) {
        this.response.setLocale(locale);
    }

    @Override
    public void setRequest(Request request) {
        this.response.setRequest(request);
    }

    @Override
    @Deprecated
    public void setStatus(int status, String message) {
        this.response.setStatus(status, message);
    }

    @Override
    public void setStatus(int status) {
        this.response.setStatus(status);
    }

    @Override
    public void setStream(OutputStream stream) {
        this.response.setStream(stream);
    }

    @Override
    public void setSuspended(boolean suspended) {
        this.response.setSuspended(suspended);
    }
/*
    @Override
    protected String toEncoded(String url, String sessionId) {
        return this.response.toEncoded(url, sessionId);
    }
*/   
}

Related Posts Plugin for WordPress, Blogger...