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); } */ }