04 agosto 2015

Revisando el Top 10, ¡ahora en Node!


Buenas, dejando por una vez la VoIP a un lado, hoy toca hablar de seguridad en Node (Node.js® / io.js). Con el implacable crecimiento de JavaScript en el lado del servidor cada vez es mayor el número de servicios que aprovechan las ventajas de este entorno. Especialmente los relacionados con la web (ej: Express, Hapi, etc), que además se están utilizando con éxito en producción atendiendo a millones de usuarios cada día.

Como suele ocurrir con las nuevas tecnologías el tema de la seguridad es más de “ya si tal”. Esto ocurre, entre otros motivos que todos conocemos, debido a los constantes cambios en la mayoría de las librerías e incluso algunos en el propio core. Así que los desarrolladores tenemos bastante con que nuestras aplicaciones no se rompan y, cuando es posible, en mejorar el rendimiento de las mismas.

A lo que vamos, en mi trabajo como programador, cuando tenía que securizar una aplicación de este tipo tocaba tirar de diapositivas de algunas conferencias, diversos posts, etc. Por este motivo empecé a pensar que era el momento de revisar como aplicarían las vulnerabilidades más comunes de la web en este entorno. En una primera investigación rápida para ver si existía algún proyecto relacionado al que poder contribuir me encontré con NodeGoat, una aplicación vulnerable que implementa el OWASP Top 10. El problema era que estaba sin actualizar, por lo que utilizaba una versión antigua de Express, perdiendo demasiadas mejoras introducidas con el tiempo. Por este mismo motivo fallaba la instalación de las mismas y era imposible ponerla en marcha. Así que me puse manos a la obra, tras una re-escritura de distintas partes y de que @ckarande (el autor) aceptase los cambios todo en orden otra vez. Simplemente tenéis que seguir los pasos del README para jugar con ella, incluso podéis desplegar de forma gratuita vuestra propia copia en Heroku con un par de clicks.

Login

Una vez instalada podemos acceder a la ruta "tutorial" (no hace falta estar logueado) y registrar un usuario o utilizar los credenciales por defecto:
  • "admin" / "Admin_123"
  • "user1" / "User1_123"
  • "user2" / "User2_123"

Tutorial
Panel administración 
Panel usuario

Podríamos repasar todas las vulnerabilidades que se explican en el tutorial pero me parece un poco sin sentido teniendo en cuenta que todos sabemos inglés. Así que vamos a comentar un par de ellas y el que tenga interés ya sabe, a probar y contribuir al proyecto ;). Por cierto, sé que es un poco coñazo tener el tutorial metido en la aplicación, pero tenemos en el Roadmap sacarlo a una carpeta de documentación en condiciones.

A1 - 1 Inyección en el lado del servidor

La típica inyección de JavaScript de toda la vida, pues eso mismo en un servidor en Node, os podéis imaginar ... Por suerte ya no es común encontrárselo por ahí. La explicación rápida es la de siempre también, no uses "eval" ni nada parecido ("setTimeout", "setInterval", "Function", etc). En este caso el problema es mayor aún, ya que el atacante podría inyectar JavaScript que modificase el comportamiento del servidor. Un caso muy sencillo sería "process.exit()" que lo pararía. Uno más divertido aún sería usar "while(1)", que mantendría el Event Loop de Node ocupado indefinidamente sin poder hacer nada más, como contestar a las peticiones de los usuarios. Un ejemplo de código vulnerable sería el siguiente:

var preTax = eval(req.body.preTax);

Corregirlo sería tan sencillo como evitar el uso de estas funciones conflictivas, por ejemplo:

var preTax = parseInt(req.body.preTax);

A continuación dejo el vídeo con una de las demo del tutorial, en donde se accede al sistema de ficheros del servidor utilizando este payload:

res.end(require('fs').readdirSync('..').toString());


Inyección: Acceso al sistema de archivos


A5 - Mala configuración de seguridad, A8 - CSRF

Express es la librería para hacer servicios web más utilizada, pero no es segura por defecto. Sí incluye algunos mecanismos que debemos configurar, pero no cubre todo lo que necesitamos. Las siguientes líneas, en orden, describen lo que se implementa a continuación.
  • Evitar el fingerprinting.
  • Cookies seguras: ID firmado y que solo se transmitan sobre HTTPS (doc.).
  • Middleware para evitar CSRF.
var csrfProtection = csrf({ cookie: true })

app.disable("x-powered-by");
app.use(express.session({
  secret: config.cookieSecret,
  key: "sessionId",
    httpOnly: true,
    cookie: {
      secure: true
    });
}

app.use(express.csrf());
app.get('/form', csrfProtection, function(req, res) {
  // pass the csrfToken to the view
})

Para suplir estas carencias existen otros "middlewares", principalmente disponemos de dos alternativas: Helmet y Lusca. Prefiero el primero simplemente por tener una mayor adopción, la idea y funcionamiento es muy similar.

// Prevents opening page in frame or iframe to protect from clickjacking
app.use(helmet.xframe());
// Prevents browser from caching and storing page
app.use(helmet.noCache());
// Allows loading resources only from white-listed domains
app.use(helmet.csp());
// Allows communication only on HTTPS
app.use(helmet.hsts());
// Forces browser to only use the Content-Type set in the response header instead of sniffing or guessing it
app.use(nosniff());

Antes de terminar me gustaría comentar que la imagen incluida al principio del post es el logo del Node Security Project, una iniciativa que tiene el objetivo de documentar las vulnerabilidades conocidas. Además ponen a nuestra disposición recursos y herramientas que nos ayudan a securizar nuestro código.

Esto es todo por hoy, en la próxima ocasión contaré como utilizar éstas y otras buenas prácticas que sigo en mis proyectos.

Artículo cortesía de Jesús Pérez