Aikido

Porque é que as variáveis globais causam fugas de dados nos servidores Node.js

Segurança

Regra
Evitar não intencional global variáveis caching.In Node.js
e Python servidores, globais variáveis persistem através de
pedidos, causando dados fugas e condições de corrida condições de corrida.

Linguagens suportadas: JavaScript, TypeScript, Python

Introdução

As variáveis globais nos servidores Node.js persistem durante o tempo de vida do processo, e não apenas num único pedido. Quando os manipuladores de pedidos armazenam dados do utilizador em variáveis globais, esses dados permanecem acessíveis a pedidos subsequentes de diferentes utilizadores. Isto cria vulnerabilidades de segurança em que os dados da sessão, os tokens de autenticação ou as informações pessoais do utilizador A vazam para o utilizador B.

Porque é importante

Implicações de segurança (fugas de dados): As variáveis globais que armazenam em cache dados específicos do utilizador criam fugas de dados entre pedidos. O estado de autenticação, os dados da sessão ou as informações pessoais de um utilizador tornam-se visíveis para outros utilizadores, violando os limites de privacidade e segurança.

Condições de corrida: Quando várias solicitações simultâneas modificam a mesma variável global, há uma grande chance de comportamento imprevisível. Os dados do utilizador A podem ser substituídos pelo pedido do utilizador B a meio do processamento, levando a cálculos incorrectos, estado corrompido ou utilizadores a verem os dados uns dos outros.

Complexidade da depuração: Os problemas causados pelo armazenamento em cache de variáveis globais são notoriamente difíceis de reproduzir porque dependem do tempo e da simultaneidade da solicitação. Os erros aparecem intermitentemente na produção sob carga, mas raramente se manifestam em testes de desenvolvimento de thread único.

Fugas de memória: As variáveis globais que acumulam dados sem limpeza crescem sem limites ao longo do tempo. Cada pedido adiciona mais dados às caches ou matrizes globais, acabando por esgotar a memória do servidor e exigindo o reinício do processo.

Exemplos de código

Não conforme:

let currentUser = null;
let requestData = {};

app.get('/profile', async (req, res) => {
    currentUser = await getUserById(req.userId);
    requestData = req.body;

    const profile = await buildUserProfile(currentUser);
    res.json(profile);
});

function buildUserProfile(user) {
    return {
        name: currentUser.name,
        data: requestData
    };
}

Porque é que está errado: As variáveis globais currentUser e requestData persistem entre pedidos. Quando vários pedidos são executados em simultâneo, o pedido do utilizador B pode substituir currentUser enquanto o buildUserProfile() do utilizador A ainda está a ser executado, fazendo com que o utilizador A veja os dados do utilizador B.

Conformidade:

app.get('/profile', async (req, res) => {
    const currentUser = await getUserById(req.userId);
    const requestData = req.body;

    const profile = buildUserProfile(currentUser, requestData);
    res.json(profile);
});

function buildUserProfile(user, data) {
    return {
        name: user.name,
        data: data
    };
}

Por que isso é importante: Todos os dados específicos do pedido são armazenados em variáveis locais com escopo para o manipulador de pedido. Cada solicitação tem um estado isolado que não pode vazar para outras solicitações simultâneas. As funções recebem dados através de parâmetros em vez de acederem ao estado global, eliminando condições de corrida.

Conclusão

Mantenha todos os dados específicos do pedido em variáveis locais ou objectos de pedido fornecidos pela sua estrutura. Use variáveis globais apenas para estado verdadeiramente compartilhado, como configuração, pools de conexão ou caches somente leitura. Quando o estado global for necessário, utilize controlos de concorrência adequados e garanta que os dados nunca são específicos do utilizador.

FAQs

Tem perguntas?

Quando é que é seguro utilizar variáveis globais no Node.js?

As variáveis globais são seguras para dados só de leitura que se aplicam a todos os pedidos: configuração da aplicação, pools de ligação à base de dados, modelos compilados ou utilitários partilhados. Nunca armazene globalmente dados específicos do pedido ou do utilizador. Se precisar de armazenar dados em cache globalmente, certifique-se de que estão corretamente codificados e que o acesso é thread-safe, ou utilize soluções de cache adequadas como o Redis.

E quanto às variáveis de nível de módulo que não são explicitamente globais?

As variáveis ao nível do módulo (const, let, var no âmbito do ficheiro) comportam-se exatamente como as globais no Node.js. Elas persistem em todos os pedidos e são compartilhadas por todos os manipuladores de pedidos simultâneos. Os mesmos riscos de vazamento de dados e condições de corrida se aplicam. Trate as variáveis de nível de módulo com o mesmo cuidado que as globais explícitas.

Como é que partilho dados entre middleware e route handlers?

Utilize as propriedades do objeto de pedido fornecidas pela sua estrutura. O Express fornece req.locals ou propriedades personalizadas em req. O Fastify tem request.decorateRequest(). Esses objetos têm escopo de solicitação e são limpos automaticamente após a conclusão da solicitação, evitando vazamentos entre solicitações.

E os padrões singleton e as instâncias de classe?

As instâncias Singleton ao nível do módulo são um estado global. Se eles mantiverem dados específicos do pedido, os mesmos problemas se aplicam. Conceba os singletons para não terem estado ou manterem apenas a configuração. Para operações com estado, crie novas instâncias por pedido ou utilize padrões de fábrica que garantam o isolamento.

Como é que detecto estes problemas durante o desenvolvimento?

Execute testes de carga com pedidos simultâneos utilizando diferentes contextos de utilizador. Condições de corrida e vazamentos de dados geralmente não aparecem com testes sequenciais. Use ferramentas como Apache Bench ou autocannon para gerar carga simultânea. Adicione um registo que inclua IDs de pedido para controlar quando os dados de um pedido aparecem noutro.

Isto aplica-se a funções sem servidor como o AWS Lambda?

Parcialmente. Cada invocação Lambda obtém um novo ambiente de execução, mas o container pode ser reutilizado entre invocações. Variáveis globais persistem entre invocações que reutilizam o mesmo container. Não confie na redefinição das globais. Siga as mesmas práticas: mantenha os dados da solicitação no escopo local.

E quanto às aplicações Python WSGI/ASGI?

Aplicam-se os mesmos princípios. Servidores web Python rodam multi-threaded ou assíncronos, então variáveis de nível de módulo são compartilhadas entre requisições. O objeto g do Flask e a injeção de dependência do FastAPI fornecem armazenamento com escopo de requisição. Django tem objetos de requisição. Use mecanismos providos pelo framework ao invés de módulos globais para dados de requisição.

Obter segurança gratuitamente

Proteja seu código, nuvem e tempo de execução em um sistema central.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados do scan em 32secs.