El otro día estaba haciendo una auditoria de una aplicación web que no tenía medidas anti-CSRF me encontré que era vulnerable a XSS almacenado si se subía un fichero que incluía HTML en un campo que luego se le mostraba al usuario.

Era gracioso porque, al no permitir paréntesis, no podías ejecutar nada en javascript fácilmente. Por lo que me decidí por insertar un EMBED a un Flash que ejecutaba el javascript :).

Pero no es de eso de lo que quería hablaros. Resulta que el problema es ¿cómo haces que el usuario suba el fichero? Pues, al no tener protección contra CSRF, uno puede pensar que sería posible si se consigue que el usuario se conecte a una página que controlamos nosotros en la que le hacemos solicitar la URL de subida de ficheros y le enganchamos nuestro perverso fichero.

Por obra y gracia del CSRF, la sesión del usuario se utilizará para la operación y él ni se dará cuenta. El problema viene cuando resulta que los navegadores han restringido precisamente la posibilidad de incluir un fichero en un FORM. Debido a cuestiones de seguridad.

Si intentas asignar el VALUE a un campo INPUT TYPE=”file” lo que obtienes depende del navegador. En Chrome se envía vacío y en Firefox da un error de seguridad, por ejemplo.

Pero vamos, que es necesaria la intervención manual del usuario para que use el diálogo de seleccionar fichero.

Pero como podrás imaginar, no está todo perdido. Buscando y buscando, me encontré con este artículo donde explica como hacerlo de una forma distinta. Pero antes de entrar en detalle, un poquito de SOP.

Ya sabéis lo que es SOP, ¿verdad? Si no, a leerse el libro de la bruja ahora mismo. Bueno, en esencia y muy simplificado viene a decir que los scripts que proviene de un origen, únicamente pueden interaccionar con ese origen. Entendiendo origen como el dominio desde el que se han cargado, más o menos.

Ya que, de lo contrario, un script malicioso en una página podría hacer barbaridades en tu nombre en cualquier otra página y esto sería el fin de todo.

En particular, las peticiones AJAX (XMLHttpRequest o XHR) que se realizan desde un script únicamente pueden salir hacia el origen del que proviene el script.

Ahora bien, resulta que hay gente que ‘necesita’ hacer peticiones entre dominios. Por lo que se han inventado varias técnicas para permitirlo de forma relativamente controlada.

Uno de esos mecanismos es Cross-Origin Resource Sharing (CORS). En este mecanismo, el servidor web le dice al navegador para qué dominios permite que se viole el SOP mediante cabeceras HTTP.

Y el navegador web envía solicitudes XHR incluyendo la cabecera Origin: en la que indica el origen para que el servidor de aplicación discrimine si acepto o no la solicitud. Pero… ¿qué pasa si la aplicación no entiende de CORS?

Pues por extraño que parezca algunos navegadores te dejan hacer cualquier petición XHR a cualquier dominio, añadiendo felizmente la cabecera Origin:. Eso sí, la vuelta no la puedes leer desde tu script a menos que el servidor envíe las cabeceras CORS adecuadas (que no lo va a hacer, claro).

Esto es precisamente lo que necesitamos para subir nuestro fichero fraudulento a una web que no entienda CORS (¿y tú conoces alguna que lo haga en tu entorno cercano?).

Por lo tanto, para subir un fichero mediante CSRF, en la página maliciosa creas la solicitud XHR que simula una solicitud HTTP que incluye las cabeceras y contenido de una subida de fichero (añades .withCredentials=”true” para que envíe las cookies del sitio)  y la envías a la URL de subida de ficheros.

Echad un ojo a los ejemplos (víctima, atacante) del artículo que citaba si queréis ver cómo se hace.