JavaScript no alfanumérico

Tengo una discusión con Adri acerca de si JavaScript es un buen lenguaje o no. En una de nuestras conversaciones critica el tipado débil de JS y enlaza el artículo Brainfuck beware: JavaScript is after you! de Patricio Palladino.

Os recomiendo encarecidamente su lectura. Es muy interesante.

Resumiendo, Patricio ha creado una herramienta con la que convertir un script JS a otro, equivalente, formado únicamente por símbolos +[](){}! ¿No me creeis? Probad a introducir esto es una consola JS de un navegador cualquiera (la de Firebug o la de Chrome servirán).

[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((+{}+[])[+!![]]+(![]+[])[!+[]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[]))+(+!![]+[])+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])))()

Debería haberos aparecido un diálogo mostrando el número 1. ¿Qué ha pasado?

Aquí hay dos factores involucrados. Primero el tipado débil de JS y su famoso type coercing permite obtener números y cadenas de los objetos [] (Array vacío) y {} (Objeto vacío). Veamos un ejemplo, ten a mano esa consola JS (el siguiente listado representa una sesión en la consola de Chrome):

> +[]
0
> +!![]
1
> +!!{}+!!{}
2
Eso debería dejar claro cómo obtener números. Veamos ahora cómo obtener caracteres.
> +{}
NaN
> +{}+{}
"NaN[object Object]"
> +{}+{}+!!!!{}
"NaN[object Object]true"
> +{}+{}+!!!!{}+!!!{}
"NaN[object Object]truefalse"
> +{}+{}+!!!!{}+!!!{}
"NaN[object Object]truefalse"
> +{}+{}+!!!!{}+!!!{}+[][0]
"NaN[object Object]truefalseundefined"
> (+{}+{}+!!!!{}+!!!{}+[][0])[25]
"s"
> (+{}+{}+!!!!{}+!!!{}+[][0])[4]
"o"
> (+{}+{}+!!!!{}+!!!{}+[][0])[19]
"r"
> (+{}+{}+!!!!{}+!!!{}+[][0])[18]
"t"
¿Ingenioso, verdad? Combinando la técnica anterior podemos eliminar los números. Ahora fijaos bien en el siguiente churro:
> [][(+{}+{}+!!!!{}+!!!{}+[][0])[25]+(+{}+{}+!!!!{}+!!!{}+[][0])[4]+(+{}+{}+!!!!{}+!!!{}+[][0])[19]+(+{}+{}+!!!!{}+!!!{}+[][0])[18]]
function sort() { [native code] }
¡Hemos llegado a una función! Bueno, tampoco tiene nada de malo. Pero fijaos en las letras en negrita ahora:
NaN[object Object]truefalseundefined
Tenemos las letras necesarias para acceder a la propiedad «constructor» luego tenemos todo lo necesario para acceder a:
[]["sort"]["constructor"]

Y ahora sí, ya la hemos liado. Porque el constructor de cualquier función de JS es Function y como todos los contructores de JS, el objeto Function es una función que, en este caso, contruye funciones. ¡Y permite construirlas a partir de cadenas! Por ejemplo, probad esto:

Function("alert(1)")()

Habremos ejecutado el cuerpo de la función que no es otro que alert(1). Así que tanto rollo con lo de «eval is Evil» (Douglas Crockford in JavaScript: the Good Parts) y resulta que tenemos por ahí el constructor Function que es casi más diabólico porque oye, eval será todo lo malo que tu quieras pero eval es un elemento del lenguaje, no una propiedad accesible desde un objeto a través de una cadena.

Si quereis saber de dónde saca los paréntesis necesarios, considerad el siguiente ejemplo:
> [][(+{}+{}+!!!!{}+!!!{}+[][0])[25]+(+{}+{}+!!!!{}+!!!{}+[][0])[4]+(+{}+{}+!!!!{}+!!!{}+[][0])[19]+(+{}+{}+!!!!{}+!!!{}+[][0])[18]]+{}
"function sort() { [native code] }[object Object]"

¿Querías té? Pues toma tres tazas: parénteis, llaves y corchetes.

Queda claro, con todo lo expuesto, que podemos traducir []["sort"]["constructor"]("alert(1)")() al alfabeto reducido formado por +[](){}!

¿Cualquiera lo diría, eh?

Solución

Pero solución ¿para qué? ¿Es esto una vulnerabilidad? A priori, no en cuanto que no deja de ser JavaScript válido. Para que la secuencia de caracteres sea válida, necesita estar dentro de un intérprete JavaScript, dentro de un entorno de ejecución. El atacante necesita inyectar el código en el HTML a través de la etiqueta <script> o manipulando las propiedades onclick, onchange, onloquesea de las entidades HTML. Las medidas de seguridad que son efectivas para evitar una inyección de código del tipo []["sort"]["constructor"]("alert(1)")() son efectivas para evitar el mismo script en otra codificación.

Entonces, ¿dónde está el problema? El problema está en un determinado tipo de seguridad que se apoya en detectar patrones maliciosos en las peticiones HTTP, son los llamados WAF, Web Application Firewall. Por citar un software muy conocido, mod_security, parte de la iniciativa OWASP. Ojo, que no estoy diciendo que mod_security sea deficiente en ningún aspecto, únicamente señalo que OWASPmod_security puede esperar encontrar un patrón como /eval(.*)/ o como /Function(.*)/ y entonces no detectar este tipo de representación alternativa.

¿Podemos hacer algo? Sería genial poder desactivar Function, que es lo que propongo intentar. Llamemos, para evitar lios, objeto nativo al objeto al que apunta Function. El problema es que no podemos modificar este objeto nativo, sólo las referencias que apuntan a él para que apunten a otro lado. Para empezar, podríamos desactivar Function (perteneciente al objeto global) en sí:

Function = null

Pero no lo hagas aun (si ya lo hiciste, abre otra sesión de la consola o haz Function = []["sort"]["constructor"]) Ahora bien, y ¿cómo desactivamos []["sort"]["constructor"]? Bueno, la solución obvia es:

[]["sort"]["constructor"] = null

Pero esto permitiría por ejemplo:

[]["sort"]["call"]["constructor"]

Si no me equivoco (necesito alguna confirmación aquí), la propiedad constructor se hereda del prototipo de las funciones, es decir, que se saca de:

Function.prototype

Vamos, que el prototipo de cualquier función apuntará a la propiedad prototype de Function y es de aquí que se toma la propiedad constructor siguiendo la cadena de prototipos. Luego como este es el punto de acceso único al objeto nativo (aparte del objeto global que desactivaremos más tarde) basta con hacer:

Function.prototype.constructor = null
Function = null

Con esto desactivaremos cualquier acceso al objeto nativo. En principio, ahora, no es posible utilizar Function para crear funciones y la jerarquía de constructores se ha roto por lo que si tenías código que confiaba en que el contructor de un objeto fuese Function para comprobar si era una función, este ya no seguirá funcionando. No obstante, siempre puedes hacer:

typeof f === 'function'

Para comprobar si el objeto f es una función.

Y eso es todo. Espero que os haya sido entretenido y útil. Cualquier comentario es bienvenido.

2 comentarios en “JavaScript no alfanumérico

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s