Nello sviluppo del software dipendiamo da componenti o artefatti sia propri che di terze parti. Una gestione flessibile delle dipendenze è essenziale per il software moderno. Gestori di pacchetti come NPM, Maven, seme or NuGet sono spesso usati per specificare le dipendenze del software. Questi strumenti sono stati progettati pensando alla praticità e alla facilità d’uso, non alla sicurezza.
Il problema
Il problema è che la flessibilità e la facilità d'uso per gli sviluppatori attirano i cattivi, che vedono le dipendenze del software come un fascino irresistibile per la loro attività. Il risultato: i malintenzionati hanno seguito tutti i possibili percorsi di attacco mostrati qui. Fonte: "Collezione di coltelli di Backstabber: una revisione degli attacchi alla catena di fornitura di software open source"
In questo post ci concentreremo sull'uso delle dichiarazioni di versione aperte, nel senso che la versione scaricata non è fissa ma deve appartenere ad un certo intervallo. In fase di compilazione, la versione esistente più alta compatibile con l'intervallo di versioni specificato viene scelta e scaricata/installata dal gestore pacchetti.
Illustriamo le dichiarazioni aperte nelle dichiarazioni di dipendenze per diversi gestori di pacchetti:
-
- NPM: pacchetto.json
{
...
“dipendenze”: {
...
“accetta”: “>=1.3.8”,
“lodash”: “~4.16.0”,
...
},
...
}
Verrà installata la versione più grande esistente non inferiore alla 1.3.8 per il pacchetto accetta, così come il più grande aggiornamento 'patch' per lodash nella gamma 4.16.x.
-
- Esperto di: pom.xml
...
...
commons-io
commons-io
PUBBLICAZIONE
...
...
L'ultima versione disponibile per commons-io (file jar) verrà aggiunta come dipendenza.
-
- pIP: setup.py
...
impostare(
...
install_requires=['pepe in grani', 'launchpadlib'],
...
)
...
Tali schemi di versione aperta hanno lati positivi e negativi. La cosa buona è che le versioni più recenti generalmente contengono miglioramenti funzionali e qualitativi, correzioni di bug e patch di sicurezza, che vengono aggiornati automaticamente. Tieni presente che per la maggior parte dei progetti reali, le correzioni non vengono trasferite alle versioni minori precedenti, tranne forse per vulnerabilità catastrofiche della sicurezza. Le versioni aperte sono utili anche per l'uso nelle librerie, per ridurre il numero di versioni che devono essere installate una volta risolte tutte le dipendenze.
Ma le gamme di versioni aperte hanno un lato negativo. Non sai esattamente quali versioni verranno installate in fase di compilazione e le build non sono ripetibili. E c'è anche a buio lato con versioni aperte. Se un malintenzionato riesce a pubblicare un componente dannoso nel repository pubblico con una versione altamente compatibile con la tua gamma aperta, la tua prossima build includerà il componente dannoso, forse anche eseguendo malware negli script di installazione che potrebbero essere eseguiti automaticamente. Offuscare il carico utile dell'attacco è un'arte.
Questo è noto come il Mancanza di blocco della versione problema.
I cattivi attori cercano sempre di mettere versioni malevole di popolari pacchetti open source. Possono ottenere l'accesso alle chiavi per i repository dei pacchetti in una fuga di notizie segreta; spesso usano l'ingegneria sociale o nascondono una dipendenza malevola annidata in un'apparentemente utile pull request. Anche alcuni autori stessi un giorno decidono che il mondo non è giusto e mordono i loro clienti con materiale di protesta nei loro pacchetti!
Ora immagina di lavorare per un'organizzazione che utilizza componenti interni oltre a componenti open source.
Se un malintenzionato conosce il nome di tali componenti interni, può riuscire a pubblicare un componente con lo stesso nome nel repository pubblico. Molti gestori di pacchetti ottengono prima i componenti pubblici e, se la versione è scelta correttamente e la versione nella dipendenza dichiarata è aperta, boom! Questo problema è denominato Confusione delle dipendenze.
Mostriamo un esempio. Supponiamo che nel nostro progetto NPM abbiamo una dipendenza da un componente privato:
-
- NPM: pacchetto.json
{
"nome": "il mio-progetto",
...
“dipendenze”: {...
“mio-dip-privato”: “>=1.0.0”,...
}
...
}
L'aggressore può creare una versione major di my-private-dep (come 99.0.0) e pubblicarla nel repository pubblico npm, con il proprio account falso (l'aggressore non ha bisogno di fare nulla con la mia organizzazione). Il gestore pacchetti NPM installerà la dipendenza dannosa, spesso con risultati devastanti.
La soluzione
Per evitare questi problemi nel processo di creazione del nostro software, dovremmo seguire norme rigorose su come dichiarare le versioni dei componenti, che dipendono dalla tecnologia utilizzata. L'importante è che una specifica versione di un pacchetto, una volta pubblicata in un repository, sia immutabile (per evitare di rompere i dipendenti, non solo per ragioni di sicurezza).
L'idea generale è quella di fissare (perno), controllando sempre che le versioni fisse dei componenti (comprese TUTTE le dipendenze transitive) siano prive di malware, e questo è possibile grazie al file di blocco offerto da molti gestori di pacchetti. Vediamo come funziona il blocco della versione per diversi gestori di pacchetti. C'è un delicato compromesso tra aggiornamenti frequenti della versione per correggere vulnerabilità note e blocco della versione per evitare build non deterministiche e potenziali attacchi alla catena di fornitura.
-
- NPM:
I gestori di pacchetti npm o Yarn utilizzano diversi file di lock (npm-shrinkwrap.json / package-lock.json o Yarn.lock, rispettivamente) che elencano le versioni fisse per tutte le dipendenze, dirette e indirette. I file di lock dovrebbero essere sotto il controllo della versione, altrimenti altri sviluppatori/nodi di build potrebbero finire con versioni diverse. Evitare l'installazione di npm a meno che durante lo sviluppo non sia necessario aggiornare le dipendenze (ad esempio per installare correzioni di sicurezza). Utilizzare il più deterministico npm ci (Clean Install) in generale, in modo che il gestore dei pacchetti utilizzi il file di blocco o termini con errore se non è presente alcun file di blocco o non corrisponde al file package.json. Se nelle versioni elencate è stata verificata la presenza di malware, il file di blocco garantisce che non accada nulla di male in fase di creazione.Per i componenti interni, si consiglia di creare un ambito NPM gestito dall'organizzazione (come @myorg) e utilizzare tale ambito nella dipendenza (come @myorg/my-private-dep), che potrebbe avere solo visibilità privata. Questo blocca confusione di dipendenza attacchi, poiché solo i membri dell'organizzazione con accesso in scrittura possono pubblicare pacchetti in tale ambito.
- NPM:
-
- Maven:
Maven/Gradle non hanno file di lock (ma vedi questo articolo di StackOverflow).Gli intervalli di versioni non vengono utilizzati con Maven/Gradle tanto quanto con altri ecosistemi. Basta evitare gamme di versioni e le meta-versioni ULTIME o RILASCIATE. Dovrebbero essere controllate anche le versioni indirette. IL Versioni Maven Plugin è uno strumento utile per il controllo della versione.
Tieni presente che Maven ha sempre avuto il concetto di ambito dell'organizzazione (la parte groupId della dipendenza) e la confusione delle dipendenze non sembra essere affatto un problema per quell'ecosistema.
- Maven:
-
- Seme:
In Python ci sono diversi strumenti per gestire i lockfile:- pipenv, che genera un file di blocco Pipfile.lock.
- poesia, che genera poetry.lock.
- pip congelare, comando che genera un require.txt che funge da lockfile. Controlla se tutte le dipendenze utilizzano versioni fisse con l'operatore ==. Quindi pip install -r require.txt utilizza le dipendenze fisse.
- Seme:
Ricorda che i file di lock sopra dovrebbero essere sotto il controllo della versione e che il comando di compilazione scelto dovrebbe utilizzare il file di lock.
Il solito repository di pacchetti utilizzato con pip (PyPI) non ha ambiti di denominazione ed è vulnerabile agli attacchi di confusione delle dipendenze. Evitare la confusione delle dipendenze nell'ecosistema Python non è facile e alcuni autori consigliano di utilizzare un repository interno per fungere da proxy per le dipendenze pubbliche recuperate da PyPI, ma prendendo prima le dipendenze private dal repository interno (-index-url dovrebbe puntare al repository interno, non a PyPI, e –extra-index-url dovrebbe essere rimosso).
Alcuni attacchi veri
Attacco Getcookies: L'attore Dustin87 ha aggiunto una dipendenza indiretta nel popolare pacchetto npm mailparser a un pacchetto dannoso con una backdoor RCE (gCOMMANDhDATAi):
JSON.stringify(req.headers).replace(/g([a-f0-9]{4})h((?:[a-f0-9]{2})+)i/gi, (o, p, v) => {})
Nonostante sia stato deprecato (nessun revisore!), mailparser ha comunque ricevuto circa 64,000 download settimanali. Si è trattato di un caso di attacco quasi mancato, poiché l'RCE non è stato effettivamente esercitatocised.
NPM pubblicato questo post con i dettagli sull'attacco getcookies.
Confusione delle dipendenze:
Alex Birsan ha scoperto nel 2021 il problema della confusione delle dipendenze e ha pubblicato un post intitolato “Come ho hackerato Apple, Microsoft e dozzine di altre aziende".
Ricordare che per npm l'ambito dell'organizzazione come @myorg dovrebbe essere riservato e i pacchetti interni dovrebbero essere modificati per utilizzare l'ambito.
Con pip, il registro pubblico comune PyPI non ha ambiti/spazi dei nomi. Ogni pacchetto privato potrebbe avere un pacchetto pubblico con lo stesso nome del pacchetto interno, ma vuoto e che potrebbe generare un errore quando utilizzato, in modo che possa essere identificato se viene recuperato accidentalmente.
Nodo-ipc:
Il proprietario del pacchetto, quando è iniziata la guerra tra Russia e Ucraina, ha iniettato codice dannoso per rimuovere file casuali, quando installati su host russi e bielorussi. Il file ssl-geospec.js stava facendo questa distinzione geografica:
È interessante notare che altri pacchetti utilizzavano versioni aperte per la dipendenza node-ipc, come il popolare framework Vue.js, e i suoi manutentori hanno ricevuto un appello urgente per fissare la dipendenza node-ipc su una versione sicura.
Questo il post contiene maggiori dettagli su questo sabotaggio, che fa un passo avanti rispetto agli altri Software di protesta affidabilità.
Osservazioni conclusive
Le versioni aperte dovrebbero mai essere utilizzato in progetti software consolidati. Rendono le build non riproducibili e gli aggressori possono sfruttarle e riuscire a iniettare malware tramite attacchi agli alberi delle dipendenze come la confusione delle dipendenze sopra menzionata.
Errori di configurazione come versioni aperte, mancanza di blocco della versione o componenti interni senza ambito dovrebbe essere evitato. La prima cosa è rilevare tali problemi, magari anche bloccando la build quando vengono rilevati, e standardizzare un protocollo d'azione.
Rilevamento automatico di difetti e configurazioni errate nelle dipendenze, segnalazione di dipendenze sospette che potrebbero essere vulnerabili ad attacchi specifici alla catena di approvvigionamento come confusione di dipendenza, il tutto con strumenti di correzione utilizzabili, è uno degli obiettivi principali di Piattaforma Xygeni.
| Per saperne di più |
| Ohm M., Plate H., Sykosch A., Meier M.: “Collezione di coltelli da pugnalato alle spalle: una revisione degli attacchi alla catena di fornitura di software open source”. DIMVA 2020. Lecture Notes in Computer Science, vol 12223. Springer – 2020 (fonte della figura dell'albero degli attacchi alle dipendenze.) |
| @adam-npm: "Modulo dannoso segnalato: getcookies“. npm Blog (archiviato) – 2 maggio 2018. |
| Alex Birsan: “Come ho hackerato Apple, Microsoft e dozzine di altre aziende”. Medio – 9 febbraio 2021. |
| Ax Sharma: “GRANDE sabotaggio: il famoso pacchetto npm cancella file per protestare contro la guerra in Ucraina" BleepingComputer – 17 marzo 2022 |





