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