La pipeline CI/CD come vettore d'attacco: il punto debole di sicurezza che si ignora

La pipeline CI/CD come vettore d'attacco: il punto debole di sicurezza che si ignora

C'è un componente, in quasi ogni azienda che sviluppa software, che ha le chiavi di tutto, eppure viene trattato come idraulica invisibile: la pipeline CI/CD. Il sistema che prende il codice, lo costruisce e lo manda in produzione ha accesso ai segreti, alle credenziali del registry, alle chiavi del server, e spesso il permesso di scrivere direttamente sull'ambiente di produzione. È, di fatto, uno degli asset più privilegiati dell'intera infrastruttura, e proprio per questo è un bersaglio. Ma mentre il codice applicativo viene revisionato, testato e sottoposto a penetration test, la pipeline che lo distribuisce viene scritta una volta, fatta funzionare, e dimenticata, come se fosse un dettaglio operativo invece di un perimetro di sicurezza. In un'attività di penetration testing, l'abuso del trust nelle pipeline CI/CD è uno dei vettori che porta più lontano con meno fatica, perché spesso nessuno l'ha mai guardato con occhi avversari. In questo articolo mostro come un attaccante usa la pipeline per arrivare in produzione, e le contromisure concrete per chiudere quei vettori prima che diventino una breccia.

Perché la pipeline CI/CD è un bersaglio appetibile?

Perché concentra in un solo punto i due ingredienti che un attaccante cerca: l'accesso ai segreti e la strada verso la produzione. Pensaci dalla prospettiva di chi attacca. Per compromettere un'applicazione puoi cercare una vulnerabilità nel codice, faticosa da trovare e da sfruttare; oppure puoi puntare alla pipeline che quel codice lo costruisce e lo distribuisce, e se la comprometti hai vinto tutto in un colpo: i segreti che usa, il codice che produce, l'ambiente su cui scrive. La pipeline è il punto in cui il codice diventa software in esecuzione, e chi controlla quel punto controlla il software.

Il problema è aggravato da un errore di percezione diffuso: la pipeline viene vista come configurazione, non come codice con privilegi. Un file di workflow è trattato come un dettaglio tecnico da far funzionare, mentre in realtà è codice eseguibile che gira con accesso ai segreti e alla produzione, e come tale andrebbe revisionato e protetto con lo stesso rigore, anzi di più, del codice applicativo. Questa cecità è esattamente ciò che rende la pipeline un bersaglio così conveniente: è potente quanto il codice di produzione e sorvegliata quanto un file di log. La sicurezza della catena di build e deploy è oggi un dominio a sé, codificato in framework dedicati come l'OWASP Top 10 per la sicurezza CI/CD, consultabile su OWASP.

La pipeline di deploy è il sistema più privilegiato che quasi nessuno protegge. Ha i segreti, ha l'accesso alla produzione, e viene trattata come plumbing invece che come il perimetro critico che è. Un attaccante lo sa; spesso è l'unico a saperlo.

Come un attaccante entra: i vettori reali

Entra sfruttando il fatto che la pipeline esegue codice e processa input, e dove c'è esecuzione di codice e input non fidato ci sono vettori d'attacco. Il primo è la workflow injection: molte pipeline costruiscono comandi inserendo al loro interno dati che provengono dall'esterno, come il titolo di una pull request, il nome di un branch o il messaggio di un commit. Se quei dati vengono interpolati in uno script senza essere trattati come non fidati, un attaccante può inserire nel titolo di una pull request un payload che, quando la pipeline lo elabora, esegue comandi arbitrari nel contesto della pipeline stessa, con tutti i suoi privilegi. È injection classica, applicata a un terreno che pochi pensano a difendere.

Il secondo vettore sono i segreti esposti nei log: una pipeline che stampa variabili d'ambiente per debug, o che esegue un comando che riecheggia un token, può finire per scrivere i propri segreti nei log di esecuzione, che spesso sono più accessibili di quanto si pensi. Il terzo, particolarmente insidioso, sono le dipendenze non bloccate: le pipeline usano componenti di terze parti, le action, riferite con un'etichetta mobile come latest o il nome di un branch. Se l'autore di quel componente viene compromesso, o se il riferimento mobile viene fatto puntare a codice malevolo, la tua pipeline esegue quel codice malevolo al prossimo run, con i tuoi segreti. È un attacco alla supply chain del processo di build, lo stesso tipo di rischio che si gestisce per le dipendenze applicative e che ho trattato per il codice nell'articolo sull'aggiornamento automatico delle dipendenze con Dependabot e Renovate.

Cosa rende pericolosa una semplice pull request?

La rende pericolosa il fatto che, in certe configurazioni, il codice di una pull request proveniente da un estraneo viene eseguito con accesso ai segreti del repository. È il vettore più sottovalutato e più grave. Per permettere alle pipeline di commentare le pull request o aggiornarne lo stato, alcune configurazioni eseguono il workflow con i privilegi e i segreti del repository di destinazione anche quando la pull request arriva da un fork esterno, cioè da chiunque. Se quella pipeline esegue codice contenuto nella pull request, per esempio lo script di build o di test che l'autore della pull request ha modificato, allora un attaccante può aprire una pull request con codice malevolo e quel codice girerà con i segreti della tua azienda, che potrà esfiltrare con un semplice comando.

Questo trasforma il gesto più aperto e collaborativo del mondo dello sviluppo, accettare contributi esterni, in una potenziale via d'ingresso. La difesa non è chiudere i contributi, è capire quali eventi della pipeline eseguono codice non fidato con privilegi, e separare nettamente i due mondi: il codice non fidato si esegue senza segreti, i segreti si usano solo su codice fidato e già revisionato. Confondere i due è l'errore che ha portato a compromissioni reali in aziende di ogni dimensione. Se vuoi capire come applico questa analisi alla tua pipeline, nel mio profilo professionale trovi l'esperienza concreta su sicurezza offensiva e hardening dei processi di sviluppo per le PMI.

Le contromisure: come si chiude il least-privilege dei token?

Si chiude dando alla pipeline il minimo dei privilegi necessari, invece del massimo per comodità. Il principio del privilegio minimo è la difesa più efficace e la più trascurata. Il token che la pipeline usa per operare ha, per default in molte configurazioni, permessi ampi: può scrivere sul repository, pubblicare pacchetti, modificare lo stato. La maggior parte dei job non ha bisogno di tutti questi permessi, e la regola corretta è partire da un accesso in sola lettura e concedere esplicitamente solo i permessi che ciascun job richiede davvero.

# permessi minimi a livello di workflow
permissions:
  contents: read

jobs:
  deploy:
    permissions:
      contents: read
      packages: write   # concesso solo dove serve davvero

La logica è che, se un attaccante riesce a compromettere un job, i danni che può fare sono limitati ai permessi di quel job: un token in sola lettura compromesso è un problema molto più piccolo di un token onnipotente compromesso. Allo stesso principio appartiene la gestione dei segreti: ogni segreto va esposto solo ai job che lo usano davvero, non all'intera pipeline, così che un job che non deve toccare le chiavi di produzione non le abbia nemmeno disponibili. Restringere i privilegi non elimina il rischio di compromissione, ma riduce drasticamente il suo raggio d'azione, ed è la differenza tra un incidente contenuto e una catastrofe.

Come ti accorgi che la pipeline è stata compromessa?

Te ne accorgi solo se la stai osservando, e la maggior parte delle aziende non guarda la propria pipeline finché non è troppo tardi. La prevenzione chiude i vettori, ma nessuna difesa è perfetta, e la capacità di rilevare un abuso è la seconda linea che distingue un incidente contenuto da uno scoperto mesi dopo. Il primo strumento è l'audit log della pipeline: ogni esecuzione, ogni modifica a un workflow, ogni accesso a un segreto lascia una traccia, e quella traccia va conservata e monitorata. Un'esecuzione partita in un orario insolito, un workflow modificato da chi non avrebbe dovuto, un job che accede a un segreto che normalmente non tocca, sono tutti segnali che, se osservati, rivelano un abuso in corso o appena avvenuto.

Il secondo strumento è la scansione dei segreti: sistemi che monitorano il codice e i log alla ricerca di credenziali esposte, così che un segreto finito per errore in un punto sbagliato venga rilevato e revocato prima che qualcuno lo sfrutti. È una rete di sicurezza per l'errore umano, che prima o poi capita a tutti. Il terzo è il monitoraggio dei comportamenti anomali della pipeline: una build che improvvisamente contatta un dominio esterno mai visto prima, un job che scarica e installa qualcosa di inatteso, un volume di traffico in uscita anomalo, sono indizi di una pipeline che sta facendo qualcosa che non dovrebbe.

La ragione per cui la rilevazione conta tanto quanto la prevenzione è che il danno di una pipeline compromessa cresce con il tempo in cui resta inosservata. Un attaccante che ottiene l'accesso ai segreti di deploy e non viene scoperto può tornare quando vuole, alterare il software che distribuisci, e usare la tua infrastruttura come base per altri attacchi. Scoprire l'abuso in ore invece che in mesi è la differenza tra un incidente gestibile e una compromissione profonda. E poiché la pipeline è codice, monitorarla significa applicare a essa gli stessi strumenti di osservabilità e di rilevamento delle anomalie che si applicano a qualunque sistema di produzione critico, perché critico lo è, anche se troppo spesso non viene trattata come tale.

Pinning, isolamento dei runner e revisione dei workflow

Sono le tre contromisure che chiudono i vettori rimanenti, e insieme al privilegio minimo costituiscono l'hardening di base di una pipeline. Il pinning risolve il problema delle dipendenze: invece di riferire un componente di terze parti con un'etichetta mobile, lo si blocca a una versione immutabile e verificata, tipicamente l'hash crittografico esatto di quella versione, così che il codice che esegui domani sia bit per bit quello che hai verificato oggi, anche se l'autore originale viene compromesso. È lo stesso principio dell'immutabilità che rende sicuro un artefatto, applicato alle dipendenze della pipeline.

L'isolamento dei runner affronta il rischio che un job compromesso contamini gli altri o l'ambiente: i runner, le macchine che eseguono i job, dovrebbero essere effimeri e isolati, creati per il singolo job e distrutti dopo, così che un compromissione non persista e non si propaghi. Eseguire codice non fidato su un runner persistente che conserva stato e credenziali tra un job e l'altro è una delle configurazioni più pericolose. Infine la revisione dei workflow: poiché i file di workflow sono codice con accesso alla produzione, le modifiche a quei file vanno revisionate come e più del codice applicativo, proteggendo il percorso che li contiene in modo che nessuno possa alterare la pipeline senza l'approvazione di chi è responsabile, esattamente come si protegge il codice critico. Un meccanismo come la designazione di proprietari obbligatori per i file sensibili garantisce che una modifica al sistema di build passi sempre sotto gli occhi giusti.

Il filo che lega tutte queste contromisure è un cambio di prospettiva: smettere di vedere la pipeline CI/CD come idraulica e iniziare a vederla per quello che è, il sistema più privilegiato e più esposto dell'infrastruttura di sviluppo. Privilegio minimo dei token, segreti esposti solo dove servono, dipendenze bloccate a versioni immutabili, runner isolati ed effimeri, e revisione obbligatoria delle modifiche ai workflow: nessuna di queste misure è complicata o costosa, e insieme trasformano la pipeline da anello debole a perimetro difeso. Il motivo per cui questi vettori restano aperti così spesso non è la difficoltà tecnica, è che nessuno guarda la pipeline con gli occhi di chi vuole abusarne. È un punto cieco culturale prima che tecnico: il team investe attenzione dove la sente, sul prodotto che i clienti vedono, e dà per scontato che l'infrastruttura che lo costruisce sia un dettaglio affidabile. Quella fiducia implicita è esattamente ciò che un attaccante sfrutta, perché la fiducia non verificata è il terreno su cui crescono le compromissioni più gravi. Trattare la pipeline con lo stesso sospetto sano che si riserva a qualunque sistema esposto, e verificarne la sicurezza invece di darla per scontata, è il cambiamento di abitudine che chiude la porta più conveniente che un attaccante possa trovare in un'azienda che sviluppa software. Portare quello sguardo offensivo sul proprio processo di build e deploy, prima che lo faccia un attaccante, è esattamente ciò che separa un'azienda che ha chiuso questi buchi da una che non sa nemmeno di averli. Se vuoi sapere quali di questi vettori sono aperti nella tua pipeline, contattami per una consulenza diretta: guardiamo insieme il tuo processo di deploy dalla parte di chi vorrebbe sfruttarlo, e lo chiudiamo prima che qualcuno ci provi.

Ultima modifica: