The Deep Tour: Prologo

Come tutti sappiamo, la rete globale ha un’estensione gigantesca e l’internet è un mondo davvero grande. E come ogni grande mondo anche esso nasconde segreti e posti oscuri. Ed è così che tutti ne parlano ma nessuno sa effettivamente cosa sia: parliamo infatti del Deep Web.
Il Deep Web o anche detto DarkNet è costituito da tutti i siti e servizi online che nostri comuni motori di ricerca non riportano nei risultati delle ricerche. Ma noi ci siamo stati e vi spiegheremo perché e sfateremo anche alcuni miti su questo mondo poco conosciuto.

Una delle più comuni rappresentazioni del Deep Web è la metafora di ciò che l’iceberg fa vedere e quello che è effettivamente invisibile a chi “naviga” nel web

Leggi tutto “The Deep Tour: Prologo”

Ada vs Injection Attacks

Secondo l’OWASP (Open Web Application Security Project) in testa alla top 10 dei più comuni bachi di sicurezza del web c’è l’injection attack, un tipo di attacco reso possibile dall’uso imprudente di dati ricevuti dall’utente.  In questo articolo mostriamo come proteggersi — a costo zero — dall’injection attack sfruttando la caratteristica di Ada di essere strongly typed.

Il problema: dati esterni &  injection attack

Avete appena avuto l’idea che vi farà ricchi: un sito web attraverso il quale vendere biciclette per cincillà (originale, non c’è che dire… forse un po’ di nicchia).  Vi create la vostra applicazione che include una pagina di login per i vostri numerosi (speriamo) clienti

Dal lato server, per autenticare il cliente,  interrogate il vostro database con qualcosa del tipo

$query = "SELECT pwd FROM clienti WHERE user='" . $_GET["usr"] . "';"
$password = $db->query($query)

Andate a dormire sognando di fare il bagno nell’oro come Paperone e la mattina dopo vi svegliate e… Il database non c’è più!  Cosa è successo?  Forse qualcosa di simile a questo

In effetti, usando come username lo strano nome nel fumetto si ottiene la query

SELECT pwd FROM clienti WHERE user='Robert'; DROP TABLE clienti; -- ';

Anche senza avere una conoscenza approfondita dell’SQL, si capisce che tale comando, oltre ad interrogare la tabella clienti, la cancella…

Questo tipo di attacco si chiama SQL Injection ed è un caso specifico di Injection attack, una delle cause più comuni di problemi di sicurezza su web. L’injection attack è reso possibile quando un dato proveniente dall’esterno (es. ricevuto da un utente, letto da un file, …) viene passato direttamente ad un interprete (shell, MySQL, …)  senza prima “depurarlo” (per esempio, quotando eventuali caratteri speciali).

In alcuni casi, come quello dell’esempio, la possibilità di un attacco di questo tipo è evidente, ma possono esistere altri casi in cui il problema è molto più nascosto se, per esempio, la query dipende dall’input dell’utente in maniera indiretta (ossia, usa dati che sono stati ricavati in un’altra porzione del codice dall’input dell’utente).

L’idea di dato contaminato (taint)

Per risolvere questo tipo di problema alcuni linguaggi (es. Ruby) introducono l’idea di dato taint (“contaminato”). Un dato letto dall’esterno è considerato “sporco” e non può essere usato con funzioni pericolose quali query o eval. Inoltre, la contaminazione è contagiosa e un risultato ottenuto elaborando un dato contaminato è contaminato a sua volta. Prima di poter passare un dato contaminato ad una funzione pericolosa è necessario dichiarare il dato  “pulito” usando —tipicamente— una specifica funzione fornita dal linguaggio (es. untaint in Ruby). Ovviamente, si suppone che il programmatore non sia pigro ed effettivamente “depuri” il dato (per esempio, quotando eventuali caratteri speciali) prima di dichiararlo pulito.

 

L’idea di dato contaminato è  chiaramente uno strumento molto utile per evitare code injection, ma è uno strumento che si presta più per i linguaggi interpretati che compilati. In molti linguaggi compilati quali C, C++, … la soluzione più semplice è forse la formulazione di opportune regole di programmazione (chiama system() solo con argomenti decontaminati!)  con pene esemplari (di fantozziana memoria) per chi sgarra

Static tainting in Ada

Anche se Ada può non sembrare una scelta intuitiva per un applicativo web, l’esistenza della libreria Ada Web Server (AWS, nulla a che fare con Amazon…) permette di implementare facilmente applicazioni robuste con interfaccia web. In particolare, per quanto riguarda la protezione da injection attack, in Ada è possibile sfruttare l’idea di dato contaminato in maniera statica (ossia verificata al momento della compilazione e non a runtime), senza overhead in fase di esecuzione.




Si consideri, per esempio il seguente “esempio giocattolo” di un package che esporta funzioni per interrogare un database

package DB_Queries is
   type Safe_Query (<>) is private;

   function Purify (Item : String) return Safe_Query;

   function MySQL_Query (Query : Safe_Query) return String;

   function "&"(Left, Right : Safe_Query) return Safe_Query;

   function "&"(Left  : Safe_Query; Right : String) 
                return Safe_Query
   is (Left & Purify(Right));  -- Syntactic sugar

   function "&"(Left  : String; Right : Safe_Query) 
                return Safe_Query
   is (Purify(Left) & Right);  -- More syntactic sugar
private
   type Safe_Query is new String;
end DB_Queries;

Un paio di osservazioni per chi non è familiare con Ada

  • In Ada  i package sono un po’ lo strumento base per organizzare il codice.  Un package tipicamente esporta una o più funzioni/procedure per operare sui tipi.  Ogni package ha un body che contiene il codice eseguibile (una specie di  *.c, insomma) ed una spec che contiene la dichiarazione delle risorse esportate verso l’esterno (all’incirca l’equivalente di un *.h).  Ogni file di spec ha poi due parti: una parte pubblica ed una parte privata, separate dalla keyword private.  Ovviamente solo la parte pubblica è visibile alla parte di  codice esterna al package.
  • La decorazione “(<>)” nella definizione pubblica di Safe_String sta ad indicare che Safe_String è un indefinite type. Spiegare nel dettaglio cosa sia un tipo indefinito ci porterebbe troppo lontano, diciamo semplicemente che si tratta di un tipo che non può essere allocato senza fornire altri dati.  Per esempio, per definire una variabile di tipo String (che è un array di caratteri) bisogna fornire la sua lunghezza, così che non posso scrivere
    X : String;  -- Quanto lunga deve essere?

    ma devo scrivere

    X : String(1..10);       -- Stringa di 10 caratteri
    Y : String := Get_Line;  -- OK, Y inizializzata col risultato di Get_Line

Nel codice sopra vediamo all’inizio la dichiarazione del tipo Safe_String che rappresenta una stringa di testo “ripulita” in modo da poter essere usata in una query SQL.  Poiché il tipo è dichiarato private, la sua struttura interna non è visibile al di fuori del package. La dichiarazione  completa di Safe_String si trova nella parte privata ed è

type Safe_String is new String;

Tale dichiarazione afferma che Safe_String è… semplicemente una stringa, ma diversa…  Il compilatore genererà quindi per Safe_String lo stesso codice che avrebbe generato per una stringa normale, ma impedirà al programmatore di mescolare direttamente i due tipi.

Questo è un aspetto importante di Ada: in C Safe_String sarebbe un sinonimo di String e il programmatore potrebbe mescolare i due tipi tra loro; in Ada Safe_String e String sono considerati due tipi diversi che non possono essere mescolati, anche se la loro implementazione interna è la stessa.  Questo tipo di separazione, se usato correttamente, è un potente strumento per ridurre il numero di errori nel codice (been there, done that…).

Più avanti vediamo la dichiarazione della funzione

function MySQL_Query (Query : Safe_Query) return String;

che, intuiamo, manda una query al database.  Si osservi che non possiamo interrogare il database con una stringa qualsiasi, ma solo con una Safe_Query, ossia una stringa che è garantita essere esente da injection attack.  Se provassimo a chiamare

Result := MySQL_Query
         ("SELECT * FROM t WHERE usr='" & Get_Param("usr") & "'" );

riceveremmo un errore in fase di compilazione poiché stiamo chiamando MySQL_Query con il tipo sbagliato, ossia una String e non una Safe_String.  L’unico modo per creare una stringa sicura è attraverso la funzione

  function Purify (Item : String) return Safe_Query;

che —possiamo supporre— verifica la presenza di caratteri speciali e,  eventualmente, li quota.  Questa funzione è l’unico cancello attraverso il quale deve passare ogni stringa prima di poter  essere usata come query, così che il programmatore non può, nemmeno volendo, passare una stringa pericolosa alla funzione MySQL_Query.

Onde permettere un minimo di elaborazione con le Safe_Query il package definisce anche tre operatori di concatenazione

function "&"(Left, Right : Safe_Query) return Safe_Query;

function "&"(Left  : Safe_Query; Right : String) 
             return Safe_Query
is (Left & Purify(Right));  -- Syntactic sugar

function "&"(Left  : String; Right : Safe_Query) 
             return Safe_Query
is (Purify(Left) & Right);  -- More syntactic sugar

Il primo può essere implementato come un alias della normale concatenazione tra stringhe (in fondo una Safe_String è pur sempre una stringa…), gli altri due permettono la concatenazione tra una stringa normale e una sicura chiamando “dietro le quinte” la funzione Purify. Come specificato dal commento, gli ultimi due operatori di concatenazione sono semplicemente zucchero sintattico, ma sono convenienti perché permettono di scrivere codice come

Result := MySQL_Query
          ("SELECT * FROM t WHERE usr='" & Purify(Get_Param("usr")) & "'" );
Si osservi come  gli operatori di concatenazione scelti dal compilatore per tradurre la linea sopra siano necessariamente quelli definiti nel package (e non l’operatore “standard” di concatenazione tra stringhe) poiché Purify restituisce una Safe_String.
La sintassi per la definizione di funzione usata nell’esempio (introdotta in Ada 2012 ) è detta espression function ed è molto conveniente.

 

Altri approcci

L’approccio presentato qui è forse il più semplice, ma non è l’unico. Due possibili esempi di approcci alternativi sono l’uso dei dynamic predicate e l’uso di package privati per controllare l’accesso a porzioni “rischiose” del  codice. Descriverli qui rischierebbe però di appesantire eccessivamente questo articolo e ne parleremo più diffusamente in un’altra occasione.

Ada per il Boeing, Ada per l’hobbista

Sul sito Tiobe.com è possibile vedere una classifica di popolarità dei linguaggi di programmazione.  Secondo tale classifica, Java è in prima posizione, seguito a ruota da C e C++. Più giù, alla 35-sima posizione, a metà strada tra LISP e PROLOG, c’è Ada, un linguaggio general purpose che, nonostante la sua scarsa popolarità, ha delle caratteristiche molto interessanti, sia per l’azienda che per l’hobbista, sia per progetti di grandi dimensioni (è molto usato in avionica) che per piccoli sistemi quali l’STM32. (Qualche anno fa al FOSDEM è stato presentato un “micro-segway” fatto con il Lego Mindstorm e programmato in multitasking con Ada.)

Con Ada si può scrivere codice sia per grandi sistemi (come il 747) che per piccoli dispositivi quali l'STM32
Con Ada si può scrivere codice sia per grandi sistemi (come il 747) che per piccoli dispositivi quali l’STM32

Lo scopo di questo breve articolo è di far conoscere Ada ad un pubblico più ampio, descrivendo brevemente lo spirito del linguaggio, alcune delle sue caratteristiche più peculiari e le risorse a disposizione del programmatore che desideri provare questo linguaggio.




Un po’ di storia.

La storia della nascita di Ada è interessante, ma troppo lunga per essere raccontata qui. Ci limitiamo soltanto a ricordare che la prima versione di Ada (nota come Ada83) risale al 1983 e che da allora sono state prodotte revisioni ogni 10 anni circa, dando origine a Ada95, Ada 2005 e Ada 2012. L’ultima revisione è un linguaggio completo, molto potente e con alcune caratteristiche  che non si trovano comunemente in altri linguaggi.

Una curiosità a proposito del nome: il linguaggio prende il nome da Ada Lovelace, figlia di Bayron (sì, il poeta) e prima programmatrice della storia. La scrittura corretta è quindi Ada e non ADA: è un nome, non un acronimo. Scrivete “ADA” in un forum e qualcuno vi correggerà in men che non si dica…

ada_lovelace_portrait
Ada Byron, contessa di Lovelace. È considerata la prima programmatrice della storia per via del suo articolo sulla macchina analitica di Babbage (una specie di calcolatore ottocentesco alimentato a vapore e con i registri fatti con ingranaggi) in cui mostra come usare la macchina per calcolare i numeri di Bernoulli

Perché Ada?

Ada nasce con l’idea di creare un linguaggio che favorisca la scrittura di codice robusto, leggibile e facile da mantenere. Mentre linguaggi come il C enfatizzano una certa semplicità (e, perché no, eleganza), cercando di fare tutto partendo da pochi concetti base e lasciando al programmatore la responsabilità di scrivere codice corretto, in Ada il programmatore è considerato un essere umano fallibile che il linguaggio deve proteggere da se stesso. Il linguaggio ha quindi una sintassi molto stringente ed una serie di strumenti che fanno sì che un programma sintatticamente corretto abbia una tale “coerenza interna” da rendere impossibili molti errori. Non è raro che un programma di media complessità funzioni perfettamente al primo tentativo o dopo un debug minimo. In un certo senso, programmare in Ada è come fare pair programming, con la differenza che il ruolo del partner è svolto dal compilatore.

Il mitico "hello world" in Ada. Ada è relativamente verboso e come sintassi ricorda il Pascal (per chi se lo ricorda). L'ambiente di sviluppo è GPS di AdaCore.
Il mitico “hello world” in Ada. Ada è relativamente verboso e come sintassi ricorda il Pascal (per chi se lo ricorda), ma è anche molto leggibile. L’ambiente di sviluppo in figura è il GPS di AdaCore.

Peculiarità di Ada

 

Poiché una descrizione di tutte le caratteristiche del linguaggio richiederebbe molto spazio e poiché molte caratteristiche di Ada (modularità, programmazione ad oggetti, numeri complessi, librerie di vario tipo) si ritrovano in altri linguaggi, qui ci limitiamo a due soli aspetti peculiari (e molto apprezzati) di Ada: la gestione del multitasking e l’uso dei contratti.

Multitasking in Ada

In Ada i task (più noti come thread al giorno d’oggi) sono oggetti nativi, con una sintassi speciale per la loro definizione e strumenti di comunicazione (rendez-vous e oggetti protetti) built-in nel linguaggio, rendendo la programmazione multitask molto “naturale.” In Ada è inoltre possibile specificare molti dettagli di basso livello quali le politiche di dispatching o le CPU sulle quali deve girare un certo task.I task non devono necessariamente essere tutti sulla stessa macchina. Ada definisce infatti strumenti che permettono la programmazione distribuita, con task che girano su macchine diverse e che comunicano tramite semplici chiamate a procedura.

Contratti, invarianti di tipo e verifica formale

A partire da Ada 2005 è possibile assegnare a funzioni e procedure dei contratti sotto forma di pre- e/o post-condizioni che sono delle espressioni booleane che devono risultare vere al momento della chiamata (pre-condizioni) e al ritorno della procedura (post-condizioni). Una cosa simile alle pre- e post-condizioni sono gli invarianti di tipo che sono espressioni booleane associate ad un tipo definito dal programmatore e che (tipicamente) vengono usate per garantirsi la “coerenza interna” di tipi complessi. Il compilatore, se richiesto, inserirà del codice per verificare che contratti ed invarianti siano sempre soddisfatti e sollevare un’eccezione nel caso contrario. Questo meccanismo riduce notevolmente i tempi di debug poiché ferma l’esecuzione alla prima manifestazione di un errore.

Il contratto di una funzione è come un contratto tra persone: la funzione promette che farà qualcosa se rispetti le sue condizioni

In Ada è anche possibile fare una verifica formale del codice, ossia, dimostrare matematicamente che il programma funziona come desiderato (es. i contratti sono sempre rispettati), evitando sessioni di test intensive. A tale scopo il codice scritto deve essere compatibile con un sotto-linguaggio di Ada (detto SPARK) che impone ulteriori restrizioni (es. l’allocazione dinamica è proibita) necessarie per la verifica formale.

ho-dimostratoOK, mi hai incuriosito. Come comincio?

Fai un salto su LibreAda da cui potrai scaricare il compilatore GNAT (basato su gcc) e il sistema di sviluppo GPS. Il sito di LibreAda è inoltre un ottimo punto di partenza per cercare informazioni e documentazione. Se cerchi librerie e tool, puoi consultare sia il sito di LibreAda  che l’Ada Information Clearinghouse (un’altra importante fonte di informazioni)  che elenca anche alcuni progetti Open Source che usano Ada.

Se sei un principiante, potrebbe interessarti l’AdaCore University un sito di e-learning gratuito dedicato ad Ada. Al momento forse non è completissimo, ma è certamente sufficiente per bootstrappare un principiante. Un’altra risorsa molto utile è il wikibook Non è completissimo al 100%, ma la maggior parte del linguaggio è coperta.

Una volta che hai passato la fase del principiante e ti senti un adaista maturo, puoi passare alla documentazione suprema: l’amato/odiato Reference Manual (RM) Si tratta dello standard ISO che descrive il linguaggio: è certamente completo (per definizione), ma è scritto in “legalese informatico” e non è la lettura più facile che ci sia. Il mio consiglio è comunque di imparare a digerirlo. Di interessante lettura sono anche i rationale che sono documenti che descrivono la differenza tra due versioni successive. Essendo più informali i rationale tendono ad essere più comprensibili. Per esempio, il rationale di Ada 2012 è http://www.ada-auth.org/standards/rationale12.html

Per parlare con altri sviluppatori Ada puoi usare il gruppo Usenet (sì, funzionano ancora) comp.lang.ada. Il rapporto segnale/rumore è decisamente buono, i troll sono pochi e i partecipanti sono (in linea di massima) gentili e pazienti con i principianti. C’è anche un gruppo LinkedIn su Ada in cui però è più facile che girino annunci (es. nuovi tool, conferenze, …) che dubbi da parte di principianti.C’è anche un gruppo reddit.