Consejos de seguridad de Express.js

TL;RD

Este texto es parte de mi nuevo libro Pro Express.js:Master Express.js—The Node.js Framework For Your Web Development [Apress, 2014]. La seguridad es importante, por eso decidí publicar este capítulo en mi blog. El libro se publicará muy pronto.

El conjunto de consejos de este capítulo trata sobre la seguridad en las aplicaciones Express.js. La seguridad es a menudo un tema descuidado que se aplaza hasta el último minuto antes del lanzamiento. Obviamente, este enfoque de tratar la seguridad como una ocurrencia tardía tiende a dejar huecos para los atacantes. Un mejor enfoque es considerar e implementar los asuntos de seguridad desde cero.

El navegador JavaScript se ha ganado una mala reputación por las vulnerabilidades de seguridad, por lo que debemos mantener nuestras aplicaciones Node.js lo más seguras posible. Con las modificaciones simples y el middleware que se tratan en este capítulo, puede abordar sin esfuerzo algunos problemas básicos de seguridad.

Este capítulo cubre los siguientes temas:

  • Falsificación de solicitud entre sitios (CSRF)
  • Permisos de proceso
  • Encabezados de seguridad HTTP
  • Validación de entrada

Falsificación de solicitud entre sitios

CSRF y el csurf El middleware se cubrió brevemente en el Capítulo 4 de Pro Express.js. Consulte ese capítulo para ver la definición y explicación de CSRF.

El csurf el middleware hace la mayor parte del trabajo de hacer coincidir los valores entrantes de las solicitudes. Sin embargo, aún necesitamos exponer los valores en las respuestas y devolverlos al servidor en plantillas (o JavaScript XHR). Primero, instalamos el csurf módulo como cualquier otra dependencia con:

$ npm install [email protected]

Luego, aplicamos csurf con app.use(), como se explica en el Capítulo 4:

app.use(csrf());

El csrf debe ir precedido de cookie-parser y express-session porque depende de este middleware (es decir, para instalar, importar y aplicar los módulos necesarios).

Una de las formas de implementar la validación es usar un middleware personalizado para pasar el token CSRF a todas las plantillas usando response.local . Este middleware personalizado debe preceder a las rutas (como es el caso de la mayoría de las declaraciones de middleware):

app.use(function (request, response, next) {
  response.locals.csrftoken = request.csrfToken();
  next();
});

En otras palabras, facilitamos manualmente la presencia del token en el cuerpo (que se muestra en este ejemplo), consulta o encabezado. (Según su preferencia o un contrato entre el cliente, puede usar la consulta o el encabezado).
Entonces, para representar el valor en la plantilla como un valor de formulario oculto, podemos usar

input(type="hidden", name="_csrf", value="#{csrftoken}")

Este campo de entrada oculto agregará el valor del token a los datos del formulario enviado, lo que facilitará el envío del token CSRF al /login ruta junto con otros campos como correo electrónico y contraseña.

Aquí está el contenido completo del lenguaje Jade en el archivo ch15/index.jade:

doctype html
html
  head
    title= title
    link(rel='stylesheet', href='https://m03s6dh33i0jtc3uzfml36au-wpengine.netdna-ssl.com/css/style.css')
  body
    if errors
      each error in errors
        p.error= error.msg
    form(method="post", action="/login")
      input(type="hidden", name="_csrf", value="#{csrftoken}")
      input(type="text", name="email", placeholder="[email protected]")
      input(type="password", name="password", placeholder="Password")
      button(type="submit") Login
    p
      include lorem-ipsum

Para ver la demostración de CSRF en ch15/app.js, inicie el servidor como lo hace normalmente con $ node app . Luego navegue a la página de inicio ubicada en http://localhost:3000. Debería ver el token en el campo oculto del formulario, como se muestra en la Figura 15–1. Tenga en cuenta que el valor de su token será diferente pero su formato será el mismo.

Figura 15–1. Token CSRF del csurf módulo insertado en el formulario para ser enviado posteriormente al /login ruta

Por cada solicitud a la página de inicio (/) o actualización de la página, obtendrá un nuevo token. Pero si aumenta el token para simular el ataque (puede hacerlo allí mismo en las Herramientas para desarrolladores de Chrome), obtendrá este error:

[Nota al margen]

Leer publicaciones de blog es bueno, pero ver cursos en video es aún mejor porque son más atractivos.

Muchos desarrolladores se quejaron de la falta de material de video de calidad asequible en Node. Es una distracción ver videos de YouTube y una locura pagar $ 500 por un curso de video de Node.

Visite Node University, que tiene cursos de video GRATUITOS en Node:node.university.

[Fin de la nota al margen]

403 Error: invalid csrf token
  at verifytoken...  

Permisos de proceso

Obviamente, normalmente es una mala idea ejecutar servicios web como root. Los desarrolladores de operaciones pueden utilizar authbind de Ubuntu para conectarse a puertos privilegiados (por ejemplo, 80 para HTTP y 443 para HTTPS) sin otorgar acceso de root.

Alternativamente, es posible eliminar los privilegios después de vincularse a un puerto. La idea aquí es que pasemos los valores de GID (ID de grupo) y UID (ID de usuario) a la aplicación Node.js y usemos los valores analizados para establecer la identidad del grupo y la identidad del usuario del proceso. Esto no funcionará en Windows, por lo que es posible que desee utilizar if/else y process.platform o NODE_ENV para hacer su código multiplataforma. Aquí hay un ejemplo de eliminación de privilegios configurando GID y UID con propiedades de process.env.GID y process.evn.UID variables ambientales:

// ... Importing modules
var app = express();
// ... Configurations, middleware and routes 
http.createServer(app).listen(app.get('port'), function(){
    console.log("Express server listening on port "
    + app.get('port'));
    process.setgid(parseInt(process.env.GID, 10));
    process.setuid(parseInt(process.env.UID, 10));
});

Encabezados de seguridad HTTP

El middleware Express.js llamado helmet (https://www.npmjs.org/package/helmet; GitHub:https://github.com/ helmetjs/helmet) es una colección de middleware relacionado con la seguridad que proporciona la mayoría de los encabezados de seguridad descritos en el artículo de Recx " Siete encabezados HTTP del servidor web que mejoran la seguridad de las aplicaciones web de forma gratuita”.

Al escribir estas líneas, helmet está en la versión 0.4.1 e incluye el siguiente middleware:

  • crossdomain :Sirve /crossdomain.xml para evitar que Flash cargue cierto contenido no deseado (consulte www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html)
  • csp :agrega una política de seguridad de contenido que permite la carga de contenido en la lista blanca
    (consulte content-security-policy.com y www.html5rocks.com/en/tutorials/security/content-security-policy)
  • hidePoweredBy :elimina X-Powered-By para evitar que se revele que está utilizando Node.js y Express.js
  • hsts :agrega seguridad de transporte estricta de HTTP para evitar que su sitio web se vea en HTTP (en lugar de HTTPS)
  • ienoopen :establece el encabezado X-Download-Options para Internet Explorer 8+ para evitar la carga de HTML no confiable en navegadores IE (consulte blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part- v-comprehensive-protection.aspx)
  • nocache :encabezados Cache-Control y Pragma para detener el almacenamiento en caché (útil para eliminar errores antiguos de los navegadores de los usuarios)
  • nosniff :establece el encabezado X-Content-Type-Options adecuado para mitigar la detección de tipo MIME (consulte msdn.microsoft.com/en-us/library/gg622941%28v=vs.85%29.aspx)
  • xframe :Establece el encabezado X-Frame-Options en DENY para evitar que su recurso se coloque
    en un marco para ataques de secuestro de clics (consulte en.wikipedia.org/wiki/Clickjacking)
  • xssFilter :establece el encabezado X-XSS-Protection para IE8+ y Chrome para proteger contra ataques XSS (consulte blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter- con-la-protección-x-xss-http-header.aspx)

Para instalar helmet , simplemente ejecute:

$ npm install [email protected]

Importa el módulo como siempre lo haces:

var helmet = require('helmet');

Luego aplique el middleware antes de las rutas. El uso predeterminado es el siguiente (ch15/app.js):

app.use(helmet());

La Figura 15–2 muestra cómo el helmet La respuesta HTTP v0.4.1 se verá cuando se use con las opciones predeterminadas:

Figura 15–2. helmet v0.4.1 Respuesta HTTP cuando se usa con opciones predeterminadas

Validación de entrada

Express.js no realiza ningún saneamiento o validación de entrada de usuario/cliente cuando utiliza body-parser o consulta como datos de entrada. Y, como todos sabemos, nunca debemos confiar en la entrada. Se puede insertar código malicioso (inyecciones XSS o SQL) en su sistema. Por ejemplo, el código JavaScript del navegador que trata como una cadena benigna puede convertirse en un ataque cuando imprime esa cadena en su página (¡especialmente si su motor de plantillas no escapa automáticamente de los caracteres especiales!).

La primera línea de defensa es verificar los datos manualmente con expresiones regulares en las rutas que aceptan datos externos. La "defensa" adicional se puede agregar en la capa de mapeo relacional de objetos, como Mongoose Schema (consulte el Capítulo 22 de Pro Experss.js).

Recuerde que la validación de front-end/navegador se realiza solo con fines de usabilidad (es decir, es más fácil de usar), no protege su sitio web de nada.

Por ejemplo, en ch15/app.js podemos implementar la validación que utiliza un patrón RegExp en el campo de correo electrónico, declaraciones if-else y el método test() para agregar un mensaje de error a la matriz de errores como este:

app.post('/login-custom', function(request, response){
  var errors = [];
  var emailRegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  if (!request.body.password) errors.push({msg: 'Password is required'});
  if (!request.body.email || !emailRegExp.test(request.body.email) ) errors.push({msg: 'A valid
email is required'});
  if (errors)
    response.render('index', {errors: errors});
  else
    response.render('login', {email: request.email});
});

A medida que agregue más rutas y campos de entrada para validar, terminará con más patrones RegExp y declaraciones if/else. Aunque eso funcionará mejor que no tener validación, el enfoque recomendado es escribir su propio módulo o usar express-validator .

Para instalar express-validator v2.4.0, ejecute:

$ npm install [email protected]

Importar express-validator en ch15/app.js:

var validator = require('express-validator');

Luego aplica express-validator después de body-parser :

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(validator());

Ahora, dentro de los controladores de solicitudes, podemos acceder a request.assert y
request.validationErrors() :

app.post('/login', function(request, response){
  request.assert('password', 'Password is required').notEmpty();
  request.assert('email', 'A valid email is required').notEmpty().isEmail();
  var errors = request.validationErrors();
  if (errors)
    response.render('index', {errors: errors});
  else
    response.render('login', {email: request.email});
});

El index.jade el archivo simplemente imprime errores de la matriz si hay alguno:

if errors
  each error in errors
    p.error= error.msg

Y el login.jade plantilla imprime el correo electrónico. Esta plantilla se procesa solo si la validación se realizó correctamente.

 p= email

Para demostrarlo, vaya a la página de inicio e intente ingresar algunos datos. Si hay errores, se le mostrará la página de inicio con errores como se muestra en la Figura 15–3. El mensaje doble "Se requiere un correo electrónico válido" proviene del hecho de que tenemos dos aserciones para el campo de correo electrónico (notEmpty y isEmail) y ambas fallan cuando el campo de correo electrónico está vacío.

Figura 15–3. Mensajes de error al usar express-validator para afirmar valores de formulario

Resumen

La seguridad es primordial, pero a menudo se descuida. Esto es especialmente cierto durante las primeras etapas de desarrollo. El proceso de pensamiento típico es así:concentrémonos en ofrecer más funciones y nos ocuparemos de la seguridad más adelante cuando estemos a punto de lanzar. Esta decisión suele tener buenas intenciones, pero rara vez se desarrolla según lo planeado. Como resultado, la seguridad de los sistemas sufre.

Con bibliotecas de middleware como csurf , helmet y express-validator , podemos obtener una buena cantidad de seguridad básica sin agregar demasiados ciclos de desarrollo.

En el próximo capítulo, cambiaremos de marcha y cubriremos algunos enfoques para usar Express.js con la biblioteca Socket.IO para vistas reactivas (es decir, actualizadas en tiempo real)...

Si le gustó esta publicación, es posible que desee explorar otros extractos de Pro Express.js:Master Express.js:el marco de Node.js para su desarrollo web, como:

  • LoopBack 101:Express.js con esteroides
  • Velas.js 101
  • Configuración de Secret Express.js

El libro en sí se enviará a imprimir muy, muy, muy pronto.