A segurança de uma aplicação web é um requisito não funcional da aplicação, aquele que o cliente não pede, mas evidente que faça parte do produto final. Não precisamos ser experts no assunto para que possamos fazer algo que proteja nossas aplicações. Neste artigo apresentarei algumas dicas de segurança que podem ser aplicada em qualquer plataforma, contudo, elas serão exemplificadas através do Express.js.

1 - O controverso header X-Powered-By

É comum aplicações web e API’s incluírem o header X-Powered-By na resposta, geralmente adicionado por algum framework ou por uma plataforma específica de desenvolvimento.

Temos aqui o escrutínio da resposta de um servidor PHP:

Escrutinando a resposta do servidor PHP

E também de uma aplicação criada através do Express na plataforma Node.js:

Escrutinando a resposta de um servidor Node.js com Express

Ambas contêm o header X-Powered-By. O problema é que essa informação é um prato cheio para quem deseja buscar por vulnerabilidades da sua aplicação, pois ela direcionará a pesquisa do malfeitor na busca de vulnerabilidades conhecidas da plataforma indicada pelo header. No entanto, um pequeno truque pode tornar a vida dos espreitadores um pouco mais difícil.

Solução: utilizar X-Powered-By em nosso favor

Podemos mudar a plataforma indicada na resposta para outra que não tenha nenhuma relação com a nossa. Em uma aplicação que utiliza Express, podemos fazer isso facilmente através de um middleware:

const express = require('express')
, app = express()
, ejs = require('ejs')
, bodyParser = require('body-parser');

app.use(bodyParser.json());

// resposta fake!
app.use((req, res, next) => {
    res.set('X-Powered-By', 'PHP/7.1.7');
    next();
});
//  depois rotas da aplicação

Escrutinando a resposta do servidor Express que finge ser outro

Qualquer requisição que chegar ao servidor enviará como resposta PHP/7.1.7 no header X-Powered-By. Em suma, estamos fingindo ser outra plataforma e a busca do malfeitor baseado na informação do header será em vão. Inclusive, essa estratégia pode ser utilizada por outros servidores populares do mercado.

2 - Post injection

Vejamos o trecho de uma API utilizando Express com o middlware body-parser que obtém o JSON enviado através de uma requisição com o método POST:

// trecho de código
app.put('/products/:id', (req, res, next) => {

    const product = req.body;
    /*
    Espera que haja as propriedades:
    
    product.title
    product.price
    product.description
    */
    new ProductDao()
    .update(product)
    .then(() => res.status(204).end())
    .catch(next);
});

Excelente! Nossa API espera que o JSON recebido e parseado pelo body-parser e acessível através de req.body tenha as propriedades title, price e description. Não faz sentido na atualização recebermos a propriedade created, pois o valor dessa propriedade é criado automaticamente pelo banco no momento da inclusão e nunca mais deve ser alterado.

Muito bem, mas o que acontecerá se o cliente enviar indevidamente um JSON com a propriedade created? Dependendo de como o update do DAO foi estruturado, essa informação pode parar dentro do nosso banco. E agora?

Solução: arrancar da requisição apenas o que nos interessa

O mais seguro é extrairmos do JSON recebido apenas as propriedades que fazem sentido no contexto de nossa API.

// trecho de código
app.put('/products/:id', (req, res, next) => {
    
    const product = {};
    // garante que o objeto tenha apenas as seguintes propriedades
    product.title = req.body.title;
    product.price = req.body.price;
    product.description = req.body.description;
    
    new ProductDao()
    .update(product)
    .then(() => res.status(204).end())
    .catch(next);
});

Esse passo também documenta a API, deixando claro quais são os parâmetros que recebe.

Podemos diminuir um pouco essa verbosidade com uma função auxiliar que chamarei de pluck (arranca, em português):

// trecho de código

// nova função
const pluck = (object, ...keys) => {

    const newObject = {};
    keys.forEach(key => newObject[key] = object[key])
    return newObject;
};

app.put('/products/:id', (req, res, next) => {
    
    const product = pluck(req.body, 'title', 'price', 'description');
    
    new ProductDao()
    .update(product)
    .then(() => res.status(204).end())
    .catch(next);
});

O ideal é sempre definirmos qual estrutura de dados nossa API espera receber. Há módulos na plataforma Node.js que permitem isso, por exemplo, o express-jsonschema. Com ele, além de arrancarmos apenas as propriedades que a API espera receber também podemos validar os tipos recebidos.

A tentação de recebermos um JSON e repassá-lo para a camada de persistência é grande devido sua facilidade. Porém, como vimos, é mais prudente arrancar do JSON recebido apenas as propriedades que fazem sentido naquele contexto. Essa exigência é ainda maior quando trabalharmos com bancos orientados a documento como MongoDB, pois parâmetros injetados no POST podem parar dentro do nosso banco por meio do documento.

3 - Leaking de tecnologia através de mensagens de erro

Não é raro nos depararmos com aplicações web, inclusive API’s que retornam para o cliente a stack trace. No entanto, a informação da pilha exibida pode dar insights sobre a tecnologia utilizada, inclusive informações de infraestrutura.

No caso do Express, há um middleware padrão de tratamento de erro que exibe uma mensagem simples. Porém, essa simplicidade torna evidente que o Express é a tecnologia utilizada no backend.

Solução: configurar nosso próprio middleware de tratamento de erro

O mais recomendado é configurarmos nossa próprio middleware de controle de erro, que deve vir como último ativado:

// código anterior omitido
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({ message: 'Houve um problema ao executar a operação. Tente mais tarde'});
});

Conclusão

Não precisamos ir muito além para aumentarmos a segurança da nossa aplicação.

E você, já conhecia as técnicas aqui apresentadas? Conhece outras dicas de segurança fáceis de aplicar, seja na plataforma Node.js ou não? Compartilhe sua experiência!