Red Hot Cyber

Sicurezza informatica, cybercrime, hack
news, e altro ancora
  • English

Trojan Source: un attacco supply chain invisibile.

Autore: Emanuele Buchicchio
Data Pubblicazione: 07/02/2022


Illustrazione di Luigi Marchionni (https://www.lmarchionni.me/)

L’articolo Trojan source: Invisible Vulnerabilities“, recentemente pubblicato da Nicholas Boucher, inizia con questo paragrafo “Che ne direste se fosse possibile ingannare i compilatori affinché producano dei file binari differenti dalla logica visibile agli occhi umani nel codice sorgente? Vi dimostriamo che fare ciò non solo è possibile, ma facilmente sfruttabile.

In realtà non ad essere ingannati non sono i compilatori, ma bensì gli sviluppatori che vedendo sullo schermo un codice sorgente diverso da quello che sarà poi inviato al compilatore, non riescono a capire quale sia il vero comportamento del software che stanno utilizzando all’interno del loro progetto.

Advertisements

Per comprendere come questo sia possibile è necessario pensare a come funziona la programmazione dei computer dei nostri giorni.

Programmi, Codice sorgente e Compilatori

La maggior parte dei programmi oggi viene scritta dagli sviluppatori con linguaggi di programmazione di “alto livello”, che poi un compilatore (cioè un altro programma) traduce in istruzioni eseguibili da parte del microprocessore.

Il compilatore analizza il codice sorgente del nostro programma, ne verifica la correttezza sintattica, e lo traduce in un programma eseguibile da uno specifico tipo di microprocessore (ad (esempio Intel x86 o ARM32). In questo processo di “traduzione” il compilatore potrebbe modificare il comportamento del programma e/o inserire (intenzionalmente o a causa di un difetto software del compilatore stesso) delle vulnerabilità nel programma che viene compilato. Da questo si comprende il ruolo critico, e spesso sottovalutato, svolto dal compilatore per la sicurezza del software.

Advertisements

Già negli anni ’80 Ken Thomson, in una celebre lezione tenuta in occasione del conferimento del “Turing award” dal titolo evocativo Reflections on trusting trust”, metteva in guardia rispetto a questo rischio. Il compilatore del linguaggio C è a sua volta un programma scritto in linguaggio C, quindi per potersi fidare del programma eseguibile prodotto dal compilatore occorre non solo fidarsi di chi ha scritto il programma, ma anche fidarsi del compilatore. Anche nel caso di compilatori open source come GCC l’analisi del codice del compilatore è un compito tutt’altro che banale e la fiducia nei confronti degli sviluppatori rimane un elemento centrale.

Per proseguire nella lettura dell’articolo possiamo considerare un compilatore come un analizzatore di testo che conosce i termini e la sintassi del linguaggio di programmazione. I caratteri utilizzati per formare il testo hanno necessità di essere codificati in un computer.

Codice sorgente e codifica del testo

Il codice sorgente del software è memorizzato ed elaborato sotto forma di file di testo. I caratteri che formano il testo, per essere elaborati da un computer, devono essere codificati in sequenze di bit (numeri binari). Uno degli standard più usati nel tempo nel mondo informatico è l’American Standard Code for Information Interchange (ASCII). La prima edizione di questo standard fu pubblicata nel 1963 e modificata varie volte nel tempo. Impiegava sette bit per codificare 128 differenti caratteri.

Advertisements

Con la diffusione delle tecnologie informatiche fu necessario estendere lo standard ASCII per consentire la codifica di caratteri provenienti da lingue diverse o necessari per specifici ambienti fino ad arrivare allo standard conosciuto come UNICODE. Questo standard consente di codificare 144.697 tra caratteri e simboli vari, inclusi gli alfabeti Right-to-Left (da destra a sinistra, usati in lingue come ad esempio Arabo ed Ebraico…). UNICODE prevede anche un sistema per controllare l’ordine secondo il quale il testo viene visualizzato denominato Bidirectional Algorithm (BiDi).

BiDi permette la visualizzazione del testo attraverso dei caratteri di controllo (ad esempio Right To Left Override, RLO, specifica di trattare il testo a cui si applica come right-to-left). I caratteri di controllo possono essere combinati tra loro e “nidificati”, consentendo di gestire la visualizzazione di un testo in modo anche molto complesso.

Advertisements

Figura 1 – Caratteri BiDi utilizzati per gli attacchi Trojan Source. Fonte [Boucher2021]

I file di testo con il codice sorgente del software, come tutti gli altri file di testo, possono essere memorizzati utilizzando una delle tante codifiche di testo disponibili. Solitamente viene scelta una codifica a livello di impostazioni di progetto o a livello di singolo file sorgente.

Figura 2 – scelta della codifica per un file contenete codice sorgente. UTF-8 è probabilmente la scelta più comune per la maggior parte degli ambienti di sviluppo recenti.

Advertisements

Come funziona l’attacco

Le regole di sintassi dei linguaggi di programmazione (e dei relativi compilatori) solitamente non consentono inserire i caratteri di controllo BiDi direttamente all’interno del codice sorgente. I caratteri di controllo possono però essere inserito all’interno di stringhe o dei commenti. I caratteri BiDi hanno però la caratteristica di ignorare i delimitatori delle stringhe e dei commenti e proprio questo dettaglio è alla base dell’attacco “Trojan Source”.

Combinando in maniera opportuna i caratteri di controllo BiDi all’interno di stringhe e commenti è possibile è infatti possibile creare dei frammenti di codice che vengono visualizzati dagli editor in modo da nascondere il reale comportamento del codice inviato al compilatore. Nell’esempio riportato qui sotto il comportamento del programma inviato all’interprete Python ed eseguito sulla macchina (a sinistra) è differente dal codice sorgente (a destra) visualizzato nell’editor dello sviluppatore.

Advertisements

Figura 3 esempio di attacco trojan source in Python. Fonte [Boucher2021]

Leggendo il codice a destra ci si aspetterebbe di trovare un valore pari a 50 in bank [‘alice’] al termine dell’esecuzione, mentre nella pratica il valore presente sarà ancora pari a 100.

Boucher e Anderson hanno dimostrato che tale tipo di attacco è possibile con i più usati linguaggi di programmazione tra cui C, C++, C#, JavaScript, Java, Rust, Go, and Python.

Provate a pensare agli effetti devastanti di questo tipo di attacco applicato agli smart contract!

Advertisements

Vettori di attacco

I principali vettori di attacco sono due e sono entrambi molto comuni tra gli sviluppatori:

  • Librerie open source di terze parti
  • Copia&incolla di frammenti di codici da siti web o forum

Tutti gli sviluppatori nel loro lavoro quotidiano utilizzano codice sorgente scritto da altri: cerchiamo su Google, troviamo un esempio riportato nella documentazione online, su un blog, oppure su Stack Overflow e poi lo incollano all’interno del codice che stiamo scrivendo. Da oggi sappiamo che il codice che incolliamo potrebbe in realtà essere diverso da quello che vediamo sullo schermo!

In tutti i progetti su cui ho lavorato negli ultimi anni sono inoltre presenti dei componenti scritti da altre persone provenienti da repository come GitHub, NPM, Maven, Nuget, …. I repository di software open source sono nati proprio con lo scopo di facilitare l’utilizzo del software all’interno di altri progetti.

Advertisements

Un’analisi del codice sorgente dei progetti open source presenti su GitHub ha dimostrato che alcuni repository GitHub contenevano al loro interno attacchi basati sui caratteri BiDi molto simili a quelli ipotizzati dagli autori dell’articolo, prima che l’articolo fosse pubblicato.

Come difendersi

L’attacco è praticamente impossibile da rilevare durante una revisione del codice da parte di una persona. Individuare e segnalare la presenza di caratteri di controllo BiDi in posizioni “sospette” è invece relativamente semplice per gli strumenti automatici di analisi del codice. Strumenti di questo tipo dovrebbero essere integrati in ogni ambiente di sviluppo.

Ad esempio, GitHub e Visual Studio hanno inserito un controllo mirato che avvisa lo sviluppatore della presenza dei caratteri di controllo potenzialmente pericolosi.

Advertisements