Aikido

Perché le variabili globali causano leak di dati nei server Node.js

Sicurezza

Regola
Evita la memorizzazione nella cache di
variabili globali non intenzionali. Nei server Node.js
e Python, le variabili globali persistono tra

le richieste, causando data leak e race condition. Linguaggi supportati: JavaScript, TypeScript, Python

Introduzione

Le variabili globali nei server Node.js persistono per l'intera durata del processo, non solo per una singola richiesta. Quando i gestori di richieste memorizzano i dati degli utenti in variabili globali, tali dati rimangono accessibili alle richieste successive provenienti da utenti differenti. Questo crea vulnerabilità di sicurezza in cui i dati di sessione, i token di autenticazione o le informazioni personali dell'utente A vengono divulgati all'utente B.

Perché è importante

Implicazioni di sicurezza (data leak): Le variabili globali che memorizzano nella cache dati specifici dell'utente creano leak di dati tra richieste diverse. Lo stato di autenticazione, i dati di sessione o le informazioni personali di un utente diventano visibili ad altri utenti, violando la privacy e i confini di sicurezza.

Race condition: Quando più richieste concorrenti modificano la stessa variabile globale, c'è un'alta probabilità di comportamenti imprevisti. I dati dell'utente A potrebbero essere sovrascritti dalla richiesta dell'utente B a metà dell'elaborazione, portando a calcoli errati, stati corrotti o utenti che visualizzano i dati altrui.

Complessità di debug: I problemi causati dalla memorizzazione nella cache tramite variabili globali sono notoriamente difficili da riprodurre perché dipendono dai tempi delle richieste e dalla concorrenza. I bug si presentano in modo intermittente in produzione sotto carico, ma raramente emergono durante i test di sviluppo a singolo thread.

Memory leak: Le variabili globali che accumulano dati senza alcuna pulizia crescono indefinitamente nel tempo. Ogni richiesta aggiunge ulteriori dati a cache o array globali, esaurendo infine la memoria del server e rendendo necessari riavvii del processo.

Esempi di codice

❌ Non 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
    };
}

Perché è errato: Le variabili globali currentUser e requestData persistono tra le richieste. Quando più richieste vengono eseguite in modo concorrente, la richiesta dell'utente B può sovrascrivere currentUser mentre buildUserProfile() dell'utente A è ancora in esecuzione, facendo sì che l'utente A visualizzi i dati dell'utente B.

✅ Conforme:

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

Perché questo è importante: Tutti i dati specifici della richiesta sono memorizzati in variabili locali con uno scope limitato al gestore della richiesta. Ciascuna richiesta dispone di uno stato isolato che non può propagarsi ad altre richieste concorrenti. Le funzioni ricevono i dati tramite parametri anziché accedere allo stato globale, eliminando le race condition.

Conclusione

Mantieni tutti i dati specifici della richiesta in variabili locali o negli oggetti di richiesta forniti dal tuo framework. Utilizza le variabili globali solo per stati realmente condivisi come configurazioni, pool di connessioni o cache di sola lettura. Quando lo stato globale è necessario, applica controlli di concorrenza adeguati e assicurati che i dati non siano mai specifici di un singolo utente.

FAQ

Hai domande?

Quando è sicuro utilizzare le variabili globali in Node.js?

Le variabili globali sono sicure per dati di sola lettura applicabili a tutte le richieste: configurazioni dell'applicazione, pool di connessioni al database, template compilati o utility condivise. Non memorizzare mai dati specifici della richiesta o dell'utente a livello globale. Se hai la necessità di memorizzare dati in cache a livello globale, assicurati che siano indicizzati correttamente e che l'accesso sia thread-safe, oppure utilizza soluzioni di caching dedicate come Redis.

E per quanto riguarda le variabili a livello di modulo che non sono esplicitamente globali?

Le variabili a livello di modulo (const, let, var nello scope del file) si comportano esattamente come le variabili globali in Node.js. Persistono in tutte le richieste e sono condivise da tutti i gestori di richieste concorrenti. Si applicano gli stessi rischi di data leak e race condition. Tratta le variabili a livello di modulo con la stessa cautela riservata a quelle esplicitamente globali.

Come posso condividere i dati tra middleware e gestori di route?

Utilizza le proprietà dell'oggetto di richiesta fornite dal tuo framework. Express mette a disposizione req.locals o proprietà personalizzate su req. Fastify dispone di request.decorateRequest(). Questi oggetti sono circoscritti all'ambito della richiesta e vengono puliti automaticamente al suo completamento, prevenendo fughe di dati tra le richieste.

Cosa dire dei pattern singleton e delle istanze di classe?

Le istanze singleton a livello di modulo costituiscono uno stato globale. Se contengono dati specifici della richiesta, si presentano gli stessi problemi. Progetta i singleton in modo che siano stateless o che contengano esclusivamente la configurazione. Per le operazioni basate su stato, crea nuove istanze per ogni richiesta o utilizza pattern di factory che garantiscano l'isolamento.

Come posso rilevare questi problemi durante lo sviluppo?

Esegui test di carico con richieste concorrenti utilizzando contesti utente differenti. Le race condition e i data leak spesso non emergono nei test sequenziali. Utilizza strumenti come Apache Bench o autocannon per generare un carico concorrente. Aggiungi log che includano gli ID delle richieste per tracciare quando i dati di una richiesta compaiono in un'altra.

Questo si applica anche alle funzioni serverless come AWS Lambda?

In parte. Ciascuna invocazione di Lambda riceve un ambiente di esecuzione nuovo, ma il container può essere riutilizzato tra un'invocazione e l'altra. Le variabili globali persistono tra le invocazioni che riutilizzano lo stesso container. Non fare affidamento sul reset delle variabili globali. Segui le stesse buone pratiche: mantieni i dati della richiesta all'interno dello scope locale.

E per quanto riguarda le applicazioni Python WSGI/ASGI?

Si applicano gli stessi principi. I server web Python eseguono codice multi-thread o asincrono, quindi le variabili a livello di modulo sono condivise tra le richieste. L'oggetto g di Flask e la dependency injection di FastAPI offrono uno storage con ambito di richiesta. Django dispone di oggetti request. Per i dati delle richieste, utilizza i meccanismi messi a disposizione dal framework anziché le variabili globali del modulo.

Metti in sicurezza ora

Metti in sicurezza il tuo codice, il cloud e il runtime in un unico sistema centralizzato.
Trova e risolvi le vulnerabilità rapidamente e automaticamente.

Nessuna carta di credito richiesta | Risultati della scansione in 32 secondi.