Está en la página 1de 16

Introducción

Esta hoja de trucos enumera las cosas que uno puede usar al desarrollar
aplicaciones seguras de Node.js. Cada elemento tiene una breve explicación
y solución que es específica del entorno Node.js.

Contexto
Las aplicaciones de Node.js están aumentando en número y no son diferentes
de otros frameworks y lenguajes de programación. Las aplicaciones de
Node.js también son propensas a todo tipo de vulnerabilidades de aplicaciones
web.

Objetivo
Esta hoja de trucos tiene como objetivo proporcionar una lista de las mejores
prácticas a seguir durante el desarrollo de las aplicaciones Node.js.

Recomendaciones
Hay varias recomendaciones diferentes para mejorar la seguridad de sus
aplicaciones Node.js. Se clasifican como:

 Seguridad de la aplicación
 Manejo de errores y excepciones
 Seguridad del servidor
 Seguridad de la plataforma

Seguridad de la aplicación
Use cadenas planas Promise
Las funciones de devolución de llamada asincrónicas son una de las
características más potentes de Node.js. Sin embargo, aumentar las capas de
anidamiento dentro de las funciones de devolución de llamada puede
convertirse en un problema. Cualquier proceso de varias etapas puede
anidarse a 10 o más niveles de profundidad. Este problema se llama Pyramid
of Doom o Callback Hell. En dicho código, los errores y resultados se pierden
dentro de la devolución de llamada. Las promesas son una buena forma de
escribir código asincrónico sin entrar en pirámides anidadas. Las promesas
proporcionan una ejecución de arriba hacia abajo mientras son asíncronas al
entregar errores y resultados a la siguiente .thenfunción.
Otra ventaja de Promises es la forma en que Promises maneja los errores. Si
se produce un error en una clase Promise, omite las .thenfunciones e invoca
la primera .catchfunción que encuentra. De esta manera, las promesas
brindan una mayor seguridad de captura y manejo de errores. Como principio,
puede hacer que todo su código asíncrono (aparte de los emisores) devuelva
promesas. Sin embargo, debe tenerse en cuenta que las llamadas de Promise
también pueden convertirse en una pirámide. Para mantenerse
completamente alejado de los infiernos de devolución de llamada, se deben
usar cadenas planas Promise. Si el módulo que está utilizando no admite
Promesas, puede convertir el objeto base en una Promesa utilizando
la Promise.promisifyAll()función.
El siguiente fragmento de código es un ejemplo de infierno de devolución de
llamada.
function func1(name, callback) {
setTimeout(function() {
// operations
}, 500);
}
function func2(name, callback) {
setTimeout(function() {
// operations
}, 100);
}
function func3(name, callback) {
setTimeout(function() {
// operations
}, 900);
}
function func4(name, callback) {
setTimeout(function() {
// operations
}, 3000);
}

func1("input1", function(err, result1){


if(err){
// error operations
}
else {
//some operations
func2("input2", function(err, result2){
if(err){
//error operations
}
else{
//some operations
func3("input3", function(err, result3){
if(err){
//error operations
}
else{
// some operations
func4("input 4", function(err, result4){
if(err){
// error operations
}
else {
// some operations
}
});
}
});
}
});
}
});
El código anterior se puede escribir de forma segura de la siguiente manera
usando la cadena plana de Promise:
function func1(name, callback) {
setTimeout(function() {
// operations
}, 500);
}
function func2(name, callback) {
setTimeout(function() {
// operations
}, 100);
}
function func3(name, callback) {
setTimeout(function() {
// operations
}, 900);
}
function func4(name, callback) {
setTimeout(function() {
// operations
}, 3000);
}

func1("input1")
.then(function (result){
return func2("input2");
})
.then(function (result){
return func3("input3");
})
.then(function (result){
return func4("input4");
})
.catch(function (error) {
// error operations
});

Establecer límites de tamaño de solicitud


El almacenamiento en búfer y el análisis de los cuerpos de solicitud pueden
requerir muchos recursos para el servidor. Si no hay límite en el tamaño de las
solicitudes, los atacantes pueden enviar solicitudes con grandes cuerpos de
solicitud para que puedan agotar la memoria del servidor o llenar el espacio
en disco. Sin embargo, fijar un límite de tamaño de solicitud para todas las
solicitudes puede no ser el comportamiento correcto, ya que algunas
solicitudes como las de cargar un archivo en el servidor tienen más contenido
para llevar en el cuerpo de la solicitud. Además, la entrada con un tipo JSON
es más peligrosa que una entrada multiparte, ya que analizar JSON es una
operación de bloqueo. Por lo tanto, debe establecer límites de tamaño de
solicitud para diferentes tipos de contenido. Puede lograr esto muy fácilmente
con express middlewares de la siguiente manera:
app.use(express.urlencoded({ limit: "1kb" }));
app.use(express.json({ limit: "1kb" }));
app.use(express.multipart({ limit:"10mb" }));
app.use(express.limit("5kb")); // this will be valid for every other content
type
Sin embargo, debe tenerse en cuenta que los atacantes pueden cambiar el
tipo de contenido de la solicitud y omitir los límites de tamaño de la
solicitud. Por lo tanto, antes de procesar la solicitud, los datos contenidos en
la solicitud deben validarse con el tipo de contenido indicado en los
encabezados de la solicitud. Si la validación del tipo de contenido para cada
solicitud afecta gravemente el rendimiento, solo puede validar tipos de
contenido específicos o solicitar más de un tamaño predeterminado.

No bloquee el bucle de eventos.


Node.js es muy diferente de las plataformas de aplicaciones comunes que
usan hilos. Node.js tiene una arquitectura basada en eventos de un solo
hilo. Por medio de esta arquitectura, el rendimiento se vuelve alto y el modelo
de programación se vuelve más simple. Node.js se implementa alrededor de
un bucle de evento de E / S sin bloqueo. Con este bucle de eventos, no hay
espera en E / S o cambio de contexto. El bucle de eventos busca eventos y los
envía a las funciones del controlador. Debido a esto, cuando se realizan
operaciones intensivas de JavaScript en la CPU, el bucle de eventos espera a
que finalicen. Es por eso que tales operaciones se llaman bloqueo. Para
superar este problema, Node.js permite asignar devoluciones de llamada a
eventos bloqueados por E / S. De esta forma, la aplicación principal no se
bloquea y las devoluciones de llamada se ejecutan de forma asincrónica. Por
lo tanto, como principio general,
Incluso si realiza operaciones de bloqueo de forma asincrónica, aún es posible
que su aplicación no funcione como se esperaba. Esto sucede si hay un código
fuera de la devolución de llamada que se basa en el código dentro de la
devolución de llamada para ejecutarse primero. Por ejemplo, considere el
siguiente código:
const fs = require('fs');
fs.readFile('/file.txt', (err, data) => {
// perform actions on file content
});
fs.unlinkSync('/file.txt');
En el ejemplo anterior, la unlinkSyncfunción puede ejecutarse antes de la
devolución de llamada, lo que eliminará el archivo antes de que se realicen las
acciones deseadas en el contenido del archivo. Tales condiciones de carrera
también pueden afectar la seguridad de su aplicación. Un ejemplo sería un
escenario en el que la autenticación se realiza en devolución de llamada y las
acciones autenticadas se ejecutan sincrónicamente. Para eliminar tales
condiciones de carrera, puede escribir todas las operaciones que dependen
unas de otras en una sola función sin bloqueo. Al hacerlo, puede garantizar
que todas las operaciones se ejecuten en el orden correcto. Por ejemplo, el
ejemplo de código anterior se puede escribir de forma no bloqueante de la
siguiente manera:
const fs = require('fs');
fs.readFile('/file.txt', (err, data) => {
// perform actions on file content
fs.unlink('/file.txt', (err) => {
if (err) throw err;
});
});
En el código anterior, la llamada para desvincular el archivo y otras
operaciones de archivo están dentro de la misma devolución de llamada. Esto
proporciona el orden correcto de operaciones.

Realizar validación de entrada


La validación de entrada es una parte crucial de la seguridad de la
aplicación. Las fallas de validación de entrada pueden dar lugar a muchos
tipos diferentes de ataques a aplicaciones. Estos incluyen inyección SQL,
secuencias de comandos entre sitios, inyección de comandos, inclusión de
archivos locales / remotos, denegación de servicio, recorrido de directorio,
inyección LDAP y muchos otros ataques de inyección. Para evitar estos
ataques, primero debe desinfectar la entrada a su aplicación. La mejor técnica
de validación de entrada es usar una lista blanca de entradas aceptadas. Sin
embargo, si esto no es posible, primero se debe verificar la entrada contra el
esquema de entrada esperado y se deben escapar las entradas
peligrosas. Para facilitar la validación de entrada en aplicaciones Node.js, hay
algunos módulos como validator y mongo-express-sanitize. Para obtener
información detallada sobre la validación de entrada.

Realizar escape de salida


Además de la validación de entrada, debe escapar de todo el contenido HTML
y JavaScript que se muestra a los usuarios a través de la aplicación para evitar
ataques de secuencias de comandos entre sitios (XSS). Puede usar
las bibliotecas escape-html o node-esapi para realizar el escape de salida.

Realizar el registro de actividad de la aplicación


El registro de la actividad de la aplicación es una buena práctica
alentada. Facilita la depuración de los errores encontrados durante el tiempo
de ejecución de la aplicación. También es útil para problemas de seguridad,
ya que puede usarse durante la respuesta a incidentes. Además, estos
registros se pueden utilizar para alimentar los sistemas de detección /
prevención de intrusiones (IDS / IPS). En Node.js, hay algunos módulos como
Winston o Bunyan para realizar el registro de actividad de la aplicación. Estos
módulos permiten la transmisión y consulta de registros. Además,
proporcionan una forma de manejar excepciones no detectadas. Con el
siguiente código, puede registrar las actividades de la aplicación tanto en la
consola como en el archivo de registro deseado.
var logger = new (Winston.Logger) ({
transports: [
new (winston.transports.Console)(),
new (winston.transports.File)({ filename: 'application.log' })
],
level: 'verbose'
});
Además, puede proporcionar diferentes transportes para que pueda guardar
errores en un archivo de registro separado y registros de aplicaciones
generales en un archivo de registro diferente.

Monitoree el ciclo de eventos


Cuando su servidor de aplicaciones se encuentra bajo un intenso tráfico de
red, es posible que no pueda atender a sus usuarios. Esto es esencialmente
un tipo de ataque de denegación de servicio (DoS) . El módulo toobusy-js
le permite monitorear el bucle de eventos. Realiza un seguimiento del tiempo
de respuesta, y cuando supera un cierto umbral, este módulo puede indicar
que su servidor está demasiado ocupado. En ese caso, puede dejar de
procesar las solicitudes entrantes y enviarles un 503 Server Too Busymensaje
para que su aplicación responda. Aquí se muestra el uso de ejemplo
del módulo toobusy-js :
var toobusy = require('toobusy-js');
var express = require('express');
var app = express();
app.use(function(req, res, next) {
if (toobusy()) {
// log if you see necessary
res.send(503, "Server Too Busy");
} else {
next();
}
});

Tomar precauciones contra la fuerza bruta


La fuerza bruta es una amenaza común para todas las aplicaciones web. Los
atacantes pueden usar la fuerza bruta como un ataque de adivinación de
contraseña para obtener contraseñas de cuenta. Por lo tanto, los
desarrolladores de aplicaciones deben tomar precauciones contra los ataques
de fuerza bruta, especialmente en las páginas de inicio de sesión. Node.js
tiene varios módulos disponibles para este propósito. Express-
bouncer , express-brute y rate-limiter son solo algunos ejemplos. Según sus
necesidades y requisitos, debe elegir uno o más de estos módulos y utilizarlos
en consecuencia. Los módulos Express-bouncer y express-brute funcionan
de manera muy similar y ambos aumentan el retraso con cada solicitud
fallida. Ambos se pueden organizar para una ruta específica. Estos módulos
se pueden usar de la siguiente manera:
var bouncer = require('express-bouncer');
bouncer.whitelist.push('127.0.0.1'); // whitelist an IP address
// give a custom error message
bouncer.blocked = function (req, res, next, remaining) {
res.send(429, "Too many requests have been made. Please wait " +
remaining/1000 + " seconds.");
};
// route to protect
app.post("/login", bouncer.block, function(req, res) {
if (LoginFailed){ }
else {
bouncer.reset( req );
}
});
var ExpressBrute = require('express-brute');

var store = new ExpressBrute.MemoryStore(); // stores state locally, don't


use this in production
var bruteforce = new ExpressBrute(store);

app.post('/auth',
bruteforce.prevent, // error 429 if we hit this route too often
function (req, res, next) {
res.send('Success!');
}
);
Además del expreso-bouncer y express-brute , el módulo limitador de
velocidad también ayuda a prevenir ataques de fuerza bruta. Permite
especificar cuántas solicitudes puede realizar una dirección IP específica
durante un período de tiempo específico.
var limiter = new RateLimiter();
limiter.addLimit('/login', 'GET', 5, 500); // login page can be requested 5
times at max within 500 seconds
El uso de CAPTCHA también es otro mecanismo común que se usa contra la
fuerza bruta. Hay módulos desarrollados para Node.js CAPTCHAs. Un módulo
común utilizado en aplicaciones Node.js es svg-captcha . Se puede usar de la
siguiente manera:
var svgCaptcha = require('svg-captcha');
app.get('/captcha', function (req, res) {
var captcha = svgCaptcha.create();
req.session.captcha = captcha.text;
res.type('svg');
res.status(200).send(captcha.data);
});
Además, el bloqueo de cuenta es una solución recomendada para mantener a
los atacantes lejos de sus usuarios válidos. El bloqueo de cuenta es posible
con muchos módulos como mangosta .

Use tokens anti-CSRF


La falsificación de solicitudes entre sitios (CSRF) tiene como objetivo realizar
acciones autorizadas en nombre de un usuario autenticado, mientras el
usuario desconoce esta acción. Los ataques CSRF generalmente se realizan
para solicitudes de cambio de estado, como cambiar una contraseña, agregar
usuarios o realizar pedidos. Csurf es un middleware express que se puede
usar para mitigar los ataques CSRF. Se puede usar de la siguiente manera:
var csrf = require('csurf');
csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, function(req, res) {
res.render('send', { csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, function(req, res) {
res.send('data is being processed');
});
Después de escribir este código, también debe agregar csrfTokena su
formulario HTML, que puede hacerse fácilmente de la siguiente manera:
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
Para obtener información detallada sobre los ataques de falsificación de
solicitudes entre sitios (CSRF) y los métodos de prevención, puede
consultar Prevención de falsificación de solicitudes entre sitios .

Eliminar rutas innecesarias


Una aplicación web no debe contener ninguna página que no sea utilizada por
los usuarios, ya que puede aumentar la superficie de ataque de la
aplicación. Por lo tanto, todas las rutas API no utilizadas deben deshabilitarse
en las aplicaciones Node.js. Esto ocurre especialmente en marcos
como Sails y Feathers , ya que generan automáticamente puntos finales API
REST. Por ejemplo, en Sails , si una URL no coincide con una ruta
personalizada, puede coincidir con una de las rutas automáticas y aún generar
una respuesta. Esta situación puede conducir a resultados que van desde la
filtración de información hasta la ejecución arbitraria de comandos. Por lo
tanto, antes de usar dichos marcos y módulos, es importante conocer las rutas
que generan automáticamente y eliminar o deshabilitar estas rutas.

Prevenir la contaminación del parámetro HTTP


La contaminación de parámetros HTTP (HPP) es un ataque en el que los
atacantes envían múltiples parámetros HTTP con el mismo nombre y esto hace
que su aplicación los interprete de una manera impredecible. Cuando se
envían varios valores de parámetros, Express los completa en una
matriz. Para resolver este problema, puede usar el módulo hpp . Cuando se
usa, este módulo ignorará todos los valores enviados para un parámetro
en req.queryy / o req.bodyy simplemente seleccionará el último valor de
parámetro enviado. Puede usarlo de la siguiente manera:
var hpp = require('hpp');
app.use(hpp());

Solo devuelve lo necesario


La información sobre los usuarios de una aplicación se encuentra entre la
información más crítica sobre la aplicación. Las tablas de usuario
generalmente incluyen campos como identificación, nombre de usuario,
nombre completo, dirección de correo electrónico, fecha de nacimiento,
contraseña y, en algunos casos, números de seguridad social. Por lo tanto, al
consultar y utilizar objetos de usuario, debe devolver solo los campos
necesarios, ya que puede ser vulnerable a la divulgación de información
personal. Esto también es correcto para otros objetos almacenados en la base
de datos. Si solo necesita un determinado campo de un objeto, solo debe
devolver los campos específicos requeridos. Como ejemplo, puede usar una
función como la siguiente siempre que necesite obtener información sobre un
usuario. Al hacerlo, solo puede devolver los campos que son necesarios para
su operación específica. En otras palabras, si solo necesita enumerar los
nombres de los usuarios disponibles,
exports.sanitizeUser = function(user) {
return {
id: user.id,
username: user.username,
fullName: user.fullName
};
};

Usar descriptores de propiedad de objeto


Las propiedades del objeto incluyen 3 atributos ocultos: writable(si es falso, el
valor de la propiedad no se puede cambiar), enumerable(si es falso, la
propiedad no se puede usar para bucles) y configurable(si es falso, la
propiedad no se puede eliminar). Al definir una propiedad de objeto a través
de la asignación, estos tres atributos ocultos se establecen en verdadero de
forma predeterminada. Estas propiedades se pueden establecer de la
siguiente manera:
var o = {};
Object.defineProperty(o, "a", {
writable: true,
enumerable: true,
configurable: true,
value: "A"
});
Además de estos, hay algunas funciones especiales para los atributos del
objeto. Object.preventExtensions()evita que se agreguen nuevas propiedades
al objeto.

Usar listas de control de acceso


La autorización evita que los usuarios actúen fuera de sus permisos
previstos. Para hacerlo, los usuarios y sus roles deben determinarse teniendo
en cuenta el principio del privilegio mínimo. Cada rol de usuario solo debe
tener acceso a los recursos que deben usar. Para sus aplicaciones Node.js,
puede usar el módulo acl para proporcionar la implementación de ACL (lista
de control de acceso). Con este módulo, puede crear roles y asignar usuarios
a estos roles.

Manejo de errores y excepciones


Manejar excepción no capturada
El comportamiento de Node.js para las excepciones no capturadas es imprimir
el seguimiento de la pila actual y luego terminar el hilo. Sin embargo, Node.js
permite la personalización de este comportamiento. Proporciona un proceso
global llamado objeto que está disponible para todas las aplicaciones
Node.js. Es un objeto EventEmitter y, en caso de una excepción no detectada,
se emite el evento uncaughtException y se lleva al bucle de eventos
principal. Para proporcionar un comportamiento personalizado para las
excepciones no detectadas, puede vincularse a este evento. Sin embargo,
reanudar la aplicación después de una excepción no detectada puede generar
más problemas. Por lo tanto, si no desea perderse ninguna excepción no
detectada, debe vincularse al evento uncaughtException y limpiar los recursos
asignados como descriptores de archivo, identificadores y similares antes de
cerrar el proceso. Se desaconseja reanudar la aplicación, ya que la aplicación
estará en un estado desconocido. Además, es importante tener en cuenta que
cuando se muestran mensajes de error al usuario en caso de una excepción
no detectada, no se debe revelar información detallada, como los seguimientos
de pila. En cambio, se deben mostrar mensajes de error personalizados a los
usuarios para no causar ninguna fuga de información.
process.on("uncaughtException", function(err) {
// clean up allocated resources
// log necessary error details to log files
process.exit(); // exit the process to avoid unknown state
});

Escuche los errores al usar EventEmitter


Al usar EventEmitter, pueden ocurrir errores en cualquier parte de la cadena
de eventos. Normalmente, si se produce un error en un objeto EventEmitter,
se llama a un evento de error que tiene un objeto Error como argumento. Sin
embargo, si no hay escuchas adjuntas a ese evento de error, el objeto Error
que se envía como argumento se genera y se convierte en una excepción no
detectada. En resumen, si no maneja los errores dentro de un objeto
EventEmitter correctamente, estos errores no manejados pueden bloquear su
aplicación. Por lo tanto, siempre debe escuchar los eventos de error al usar
objetos EventEmitter.
var events = require('events');
var myEventEmitter = function(){
events.EventEmitter.call(this);
}
require('util').inherits(myEventEmitter, events.EventEmitter);
myEventEmitter.prototype.someFunction = function(param1, param2) {
//in case of an error
this.emit('error', err);
}
var emitter = new myEventEmitter();
emitter.on('error', function(err){
//Perform necessary error handling here
});
Manejar errores en llamadas asincrónicas
Los errores que ocurren dentro de las devoluciones de llamada asincrónicas
son fáciles de omitir. Por lo tanto, como principio general, el primer argumento
para las llamadas asincrónicas debe ser un objeto Error. Además, las rutas
expresas manejan los errores en sí, pero siempre debe recordarse que los
errores ocurridos en llamadas asincrónicas realizadas dentro de las rutas
expresas no se manejan, a menos que se envíe un objeto Error como primer
argumento.

Los errores en estas devoluciones de llamada se pueden propagar tantas


veces como sea posible. Cada devolución de llamada a la que se ha
propagado el error puede ignorar, manejar o propagar el error.

Seguridad del servidor


Establezca las banderas de cookies adecuadamente
En general, la información de la sesión se envía utilizando cookies en
aplicaciones web. Sin embargo, el uso incorrecto de las cookies HTTP puede
hacer que una aplicación presente varias vulnerabilidades de administración
de sesión. Hay algunos indicadores que se pueden configurar para cada
cookie para evitar este tipo de ataques. httpOnly, Securey las SameSitemarcas
son muy importantes para las cookies de sesión. httpOnlyEl indicador evita que
JavaScript del lado del cliente acceda a la cookie. Esta es una contramedida
efectiva para los ataques XSS. SecureEl indicador permite que la cookie se
envíe solo si la comunicación se realiza a través de HTTPS.SameSiteEl
indicador puede evitar que se envíen cookies en solicitudes entre sitios, lo que
ayuda a proteger contra ataques de falsificación de solicitudes entre sitios
(CSRF). Además de estos, hay otros indicadores como dominio, ruta y
caduca. Se recomienda establecer estos indicadores de manera adecuada,
pero se relacionan principalmente con el alcance de las cookies y no con la
seguridad de las cookies. El uso de muestra de estos indicadores se da en el
siguiente ejemplo:
var session = require('express-session');
app.use(session({
secret: 'your-secret-key',
key: 'cookieName',
cookie: { secure: true, httpOnly: true, path: '/user', sameSite: true}
}));

Use encabezados de seguridad apropiados


Existen varios encabezados de seguridad HTTP diferentes que pueden
ayudarlo a prevenir algunos vectores de ataque comunes. Estos se enumeran
a continuación:
 Strict-Transport-Security : HTTP Strict Transport Security (HSTS) dicta a los
navegadores que solo se puede acceder a la aplicación a través de conexiones
HTTPS. Para usarlo en su aplicación, agregue los siguientes códigos:

app.use(helmet.hsts()); // default configuration


app.use(helmet.hsts("<max-age>", "<includeSubdomains>")); // custom
configuration

 X-Frame-Options : determina si una página se puede cargar a través de un \ o un \

app.use(hemlet.xframe()); // default behavior (DENY)


helmet.xframe('sameorigin'); // SAMEORIGIN
helmet.xframe('allow-from', 'http://alloweduri.com'); //ALLOW-FROM uri

 Protección X-XSS : como se describe en la Hoja de trucos de prevención XSS , este


encabezado permite a los navegadores dejar de cargar páginas cuando los
navegadores detectan ataques de secuencias de comandos en sitios cruzados. Para
implementar este encabezado en su aplicación, puede usar el siguiente código:

var xssFilter = require('x-xss-protection');


app.use(xssFilter());

 X-Content-Type-Options : incluso si el servidor establece unContent-


Typeencabezadoválidoen la respuesta, los navegadores pueden intentar detectar el
tipo MIME del recurso solicitado. Este encabezado es una forma de detener este
comportamiento y decirle al navegador que no cambie los tipos MIME especificados
en elContent-Typeencabezado. Se puede configurar de la siguiente manera:

app.use(helmet.noSniff());

 Política de seguridad de contenido : la Política de seguridad de contenido se ha


desarrollado para reducir el riesgo de ataques como Cross-Site Scripting (XSS )
y Clickjacking . Básicamente, permite el contenido de una lista blanca que usted
decida. Tiene varias directivas, cada una de las cuales prohíbe cargar un tipo
específico de contenido. Puede consultar la Hoja de trucos de la Política de seguridad
de contenido para obtener una explicación detallada de cada directiva y cómo
usarla. Puede implementar esta configuración en su aplicación de la siguiente
manera:

const csp = require('helmet-csp')


app.use(csp({
directives: {
defaultSrc: ["'self'"], // default value for all directives that are
absent
scriptSrc: ["'self'"], // helps prevent XSS attacks
frameAncestors: ["'none'"], // helps prevent Clickjacking attacks
imgSrc: ["'self'", "'http://imgexample.com'"],
styleSrc: ["'none'"]
}
}))

 Cache-Control y Pragma : el encabezado Cache-Control se puede usar para evitar


que los navegadores almacenen en caché las respuestas dadas. Esto debe hacerse
para las páginas que contienen información confidencial sobre el usuario o la
aplicación. Sin embargo, deshabilitar el almacenamiento en caché de páginas que no
contienen información confidencial puede afectar seriamente el rendimiento de la
aplicación. Por lo tanto, el almacenamiento en caché solo debe deshabilitarse para
las páginas que devuelven información confidencial. El código siguiente puede utilizar
fácilmente los controles y encabezados de almacenamiento en caché adecuados:

app.use(helmet.noCache());
El código anterior establece los encabezados Cache-Control, Surrogate-
Control, Pragma y Expires en consecuencia.

 X-Download-Options: este encabezado evita que Internet Explorer ejecute archivos


descargados en el contexto del sitio. Esto se logra con la directiva noopen. Puede
hacerlo con el siguiente código:

app.use(helmet.ieNoOpen());

 Expect-CT : Certificate Transparency es un nuevo mecanismo desarrollado para


solucionar algunos problemas estructurales relacionados con la infraestructura SSL
actual. El encabezado Expect-CT puede hacer cumplir los requisitos de transparencia
del certificado. Se puede implementar en su aplicación de la siguiente manera:

var expectCt = require('expect-ct');


app.use(expectCt({ maxAge: 123 }));
app.use(expectCt({ enforce: true, maxAge: 123 }));
app.use(expectCt({ enforce: true, maxAge: 123, reportUri:
'http://example.com'}));

 Public-Key-Pins : este encabezado aumenta la seguridad de HTTPS. Con este


encabezado, una clave pública criptográfica específica se asocia con un servidor web
específico. Si el servidor no utiliza las claves ancladas en el futuro, el navegador
considera las respuestas como ilegítimas. Se puede usar de la siguiente manera:

app.use(helmet.hpkp({
maxAge: 123,
sha256s: ['Ab3Ef123=', 'ZyxawuV45='],
reportUri: 'http://example.com',
includeSubDomains: true
}));
La decisión de usar la fijación de clave pública debe tomarse con cuidadosa
consideración, ya que puede causar el bloqueo de los usuarios durante mucho
tiempo si se usa incorrectamente.

 X-Powered-By: el encabezado X-Powered-By se usa para informar qué tecnología


se usa en el lado del servidor. Este es un encabezado innecesario que causa una
fuga de información, por lo que debe eliminarse de su aplicación. Para hacerlo, puede
usar lo hidePoweredBysiguiente:

app.use(helmet.hidePoweredBy());
Además, puede mentir sobre las tecnologías utilizadas con este
encabezado. Por ejemplo, incluso si su aplicación no usa PHP, puede
configurar el encabezado X-Powered-By para que parezca así.
app.use(helmet.hidePoweredBy({ setTo: 'PHP 4.2.0' }));
Seguridad de la plataforma
Mantenga sus paquetes actualizados
La seguridad de su aplicación depende directamente de la seguridad de los
paquetes de terceros que utiliza en su aplicación. Por lo tanto, es importante
mantener sus paquetes actualizados. Debe tenerse en cuenta que el uso de
componentes con vulnerabilidades conocidas todavía se encuentra en el
OWASP Top 10. Puede usar OWASP Dependency-Check para ver si alguno
de los paquetes utilizados en el proyecto tiene una vulnerabilidad
conocida. También puede usar Retire.js para verificar las bibliotecas de
JavaScript con vulnerabilidades conocidas. Para usarlo, puede ejecutar los
siguientes comandos en la carpeta de código fuente de su aplicación:
npm install -g retire
retire
Hay varias otras herramientas que puede usar para verificar sus
dependencias. Una lista más completa se puede encontrar en Vulnerable
Dependency Management CS .

No utilizar funciones peligrosas.


Hay algunas funciones de JavaScript que son peligrosas y solo deben usarse
donde sea absolutamente necesario. En la mayor medida posible, se debe
evitar el uso de tales funciones y módulos. El primer ejemplo es
la eval()función. Esta función toma un argumento de cadena y lo ejecuta como
cualquier otro código fuente de JavaScript. Combinado con la entrada del
usuario, este comportamiento lleva inherentemente a la vulnerabilidad de
ejecución remota de código. Del mismo modo, las llamadas
a child_process.exectambién son muy peligrosas. Esta función actúa como un
intérprete bash y envía sus argumentos a / bin / sh. Al inyectar información a
esta función, los atacantes pueden ejecutar comandos arbitrarios en el
servidor.
Además de estas funciones, hay algunos módulos que requieren un cuidado
especial cuando se usan. Como ejemplo, el fsmódulo maneja las operaciones
del sistema de archivos. Sin embargo, si la entrada de usuario desinfectada de
forma inadecuada se introduce en este módulo, su aplicación puede volverse
vulnerable a la inclusión de archivos y vulnerabilidades transversales de
directorios. Del mismo modo, el vmmódulo proporciona API para compilar y
ejecutar código en contextos de máquina virtual V8. Dado que puede realizar
acciones peligrosas por naturaleza, debe usarse dentro de un entorno limitado.
No sería justo decir que estas funciones y módulos no deberían usarse en
absoluto, sin embargo, deberían usarse con cuidado, especialmente cuando
se usan con la entrada del usuario. Además, hay otras funciones que pueden
hacer que su aplicación sea vulnerable.
Manténgase alejado de las expresiones regulares
malvadas
La expresión regular denegación de servicio (ReDoS) es un ataque de
denegación de servicio, que explota el hecho de que la mayoría de las
implementaciones de expresiones regulares pueden llegar a situaciones
extremas que hacen que funcionen muy lentamente (relacionadas
exponencialmente con el tamaño de entrada). Un atacante puede hacer que
un programa que usa una Expresión regular entre en estas situaciones
extremas y luego se cuelgue durante mucho tiempo.

La Denegación de servicio de expresión regular (ReDoS) es un tipo de ataque


de Denegación de servicio que utiliza expresiones regulares. Algunas
implementaciones de expresiones regulares (Regex) causan situaciones
extremas que hacen que la aplicación sea muy lenta. Los atacantes pueden
usar tales implementaciones de expresiones regulares para hacer que la
aplicación entre en estas situaciones extremas y se cuelgue durante mucho
tiempo. Tales expresiones regulares se denominan malvadas si la aplicación
puede atascarse en una entrada diseñada. En general, estas expresiones
regulares se explotan agrupando con repetición y alternando con
superposición. Por ejemplo, la siguiente expresión regular^(([a-z])+.)+[A-
Z]([a-z])+$se puede usar para especificar nombres de clase Java. Sin
embargo, una cadena muy larga (aaaa ... aaaaAaaaaa ... aaaa) también puede
coincidir con esta expresión regular. Hay algunas herramientas para verificar
si una expresión regular tiene el potencial de causar denegación de
servicio. Un ejemplo es vuln-regex-detector .

Ejecute linters de seguridad periódicamente


Al desarrollar código, tener en cuenta todos los consejos de seguridad puede
ser realmente difícil. Además, hacer que todos los miembros del equipo
obedezcan estas reglas es casi imposible. Esta es la razón por la que hay
herramientas de análisis de seguridad de análisis estático (SAST). Estas
herramientas no ejecutan su código, sino que simplemente buscan patrones
que pueden contener riesgos de seguridad. Como JavaScript es un lenguaje
dinámico y de tipo suelto, las herramientas de enlace son realmente esenciales
en el ciclo de vida del desarrollo de software. Estas herramientas deben
ejecutarse periódicamente y los resultados deben ser auditados. Otra ventaja
de estas herramientas es la característica de que puede agregar reglas
personalizadas para patrones que puede ver peligrosos. ESLint y JSHint son
herramientas SAST de uso común para la alineación de JavaScript.

Usar modo estricto


JavaScript tiene una serie de características heredadas peligrosas e inseguras
que no deben usarse. Para eliminar estas características, ES5 incluyó un
modo estricto para desarrolladores. Con este modo, se arrojan errores que
antes eran silenciosos. También ayuda a los motores de JavaScript a realizar
optimizaciones. Con el modo estricto, la sintaxis incorrecta previamente
aceptada causa errores reales. Debido a estas mejoras, siempre debe usar el
modo estricto en su aplicación. Para habilitar el modo estricto, solo necesita
escribir "use strict";encima de su código.
El siguiente código generará un ReferenceError: Can't find variable: yen la
consola que no se mostrará a menos que se utilice el modo estricto.
"use strict";

func();
function func() {
y = 3.14; // This will cause an error (y is not defined)
}

Adherirse a los principios generales de seguridad de


la aplicación.
Esta lista se ha centrado principalmente en problemas que son comunes en
las aplicaciones Node.js. Además, las recomendaciones contra estos
problemas se dan específicamente para el entorno Node.js. Además de estos,
existen principios generales de seguridad por diseño que se aplican a las
aplicaciones web independientemente de las tecnologías utilizadas en el
servidor de aplicaciones. También debe tener en cuenta esos principios al
desarrollar sus aplicaciones. Además, siempre puede consultar la serie
OWASP Cheat Sheet para obtener más información sobre las vulnerabilidades
de las aplicaciones web y las técnicas de mitigación utilizadas en su contra.

Recursos adicionales sobre la


seguridad de Node.js
Awesome Node.js Security resources

También podría gustarte