Categories: ⚔️, Hacking17.9 min read

Manual SQL Injection

La tecnica di attacco nota come SQL injection viene utilizzata per attaccare applicazioni, che gestiscono dati attraverso database relazionali, sfruttando il linguaggio SQL. In questo articolo vedremo di cosa si tratta e come attuare diversi attacchi di SQL injection “manualmente”, senza cioè fare ricorso a tool automatici, come il celebre SQLmap.

Che cosa è l’SQL?

Il linguaggio SQL (Structured Query Language) è il linguaggio standard per comunicare con i database relazionali. Affonda le sue origini nei primi anni ’70 presso i laboratori di ricerca IBM a San Jose, in California, in occasione di un progetto chiamato System R. Tale progetto aveva l’obiettivo di sviluppare un sistema per gestire i dati in modo relazionale, piuttosto che attraverso il tradizionale metodo gerarchico o in rete.

Il Dr. Edgar F. Codd, matematico e informatico presso IBM, nel 1970 ha pubblicato un influente documento intitolato “A Relational Model of Data for Large Shared Data Banks, diventato la base per lo sviluppo del linguaggio SQL. Questo modello proponeva un nuovo modo di organizzare e interrogare i dati usando il concetto di “relazioni”, che oggi conosciamo come “tabelle”.

La prima implementazione pratica di SQL è avvenuta in System R, e da lì il linguaggio ha iniziato a guadagnare popolarità. Durante la fine degli anni ’70 e l’inizio degli anni ’80, molte aziende, tra cui Oracle, Microsoft e altri, hanno iniziato a sviluppare i propri sistemi RDBMS utilizzando SQL come linguaggio di interrogazione principale.

Nel 1986, l’American National Standards Institute (ANSI) ha standardizzato SQL.

Ma che cos’è un database? Nel mondo digitale, un database è un contenitore di informazioni. Simile ad un armadio pieno di cassetti, dove ogni cosa è al suo posto. Così, invece di cercare un qualcosa in una stanza disordinata, possiamo semplicemente aprire il cassetto dell’armadio e trovare ciò che ci serve.

Tra le informazioni contenute nel database esistono diverse tabelle, caratterizzate da relazioni. Per immaginarlo, possiamo prendere come metafora l’organizzazione di una scuola:

  • Studenti dove ogni riga rappresenta uno studente, con colonne quali Nome, Cognome, Età e Classe
  • Insegnanti con colonne come il Nome, la Materia e la classe dove insegna.
  • Classi con colonne come Nome classe, Aula e Orario

I collegamenti tra le tabelle rappresentano le “relazioni”. Se vogliamo sapere, ad esempio, dove e quando uno studente ha lezioni di Informatica, possiamo relazionare le informazioni nelle tabelle per ottenere la risposta.

Tipologie di database

Abbiamo parlato di database relazionali, me ne esistono diverse tipologie, ognuna adatta a scopi e necessità specifiche:

  1. Database Relazionali (RDBMS): questi sono i database più comuni, basati sul modello relazionale introdotto da Edgar F. Codd. Memorizzano i dati in tabelle, con righe e colonne, e le tabelle possono essere collegate tra loro attraverso relazioni.
    Alcuni esempi sono: MySQL, Oracle, Microsoft SQL Server e PostgreSQL.
  2. Database NoSQL: questi sono stati progettati per superare le limitazioni dei database relazionali, specialmente in termini di scalabilità e flessibilità.
    Esistono diverse sottocategorie di database NoSQL:

    • Document-based: memorizzano i dati in documenti, solitamente in formato JSON.
      Esempi: MongoDB, CouchDB.
    • Key-Value Stores: semplici database che memorizzano dati come una raccolta di coppie chiave-valore.
      Esempi: Redis, Riak.
    • Column-based: progettati per memorizzare e gestire dati in colonne piuttosto che in righe.
      Esempi: Cassandra, HBase.
    • Graph-based: Ottimizzati per gestire dati sotto forma di grafi, utili per rappresentare relazioni complesse.
      Esempi: Neo4j, OrientDB.
  3. Database Object-Oriented: questi database trattano i dati come oggetti, similmente a come farebbe un linguaggio di programmazione orientato agli oggetti.
    Esempi: ObjectDB, db4o.
  4. Database Ierarchici: antenati dei database relazionali, organizzano i dati in una struttura ad albero, dove ogni record ha un unico genitore. Sono oggi meno comuni.
  5. Database di Reti: simili ai database ierarchici, ma con una struttura più flessibile, permettono a ciascun record di avere più genitori.
    Esempi: IBM IMS.
  6. Database Temporali: memorizzano informazioni relative al tempo, come intervalli di tempo o periodi di validità.
  7. Database Spaziali: ottimizzati per memorizzare e interrogare dati relativi alle posizioni fisiche, come mappe o coordinate geografiche.
    Esempi: PostGIS (estensione di PostgreSQL).
  8. Database In-Memory (IMDB): questi database mantengono l’intero set di dati in memoria (anziché sul disco), caratteristica che li rende estremamente veloci.
    Esempi: Redis (anche se può essere usato come un key-value store), SAP HANA.
  9. Database Multimodali: combinano le caratteristiche di più tipi di database, come relazionali e NoSQL, in un unico sistema.
    Esempi: ArangoDB, OrientDB.

 

Sintassi del Linguaggio SQL

Per capire come perpetrare un attacco di SQL injection, dobbiamo prima di tutto apprendere la sintassi base.

Nel linguaggio SQL i comandi chiave (come SELECT, CREATE, INSERT, ecc.) non sono sensibili al maiuscolo/minuscolo, quindi scriverli in maiuscolo o minuscolo non influenza la funzionalità del comando. Tuttavia, scriveremo i comandi in maiuscolo per migliorarne la leggibilità.

Da notare che i nomi dei database sono “case sensitive”.

Ipotizziamo di voler creare un nuovo database. Per farlo, utilizzeremo la seguente sintassi:

CREATE DATABASE thehackingquest;
  • CREATE DATABASE: sintassi chiave del linguaggio SQL che indica all’RDBMS (Sistema di Gestione di Database Relazionale) di creare un nuovo database. È un comando DDL (Data Definition Language) che definisce o modifica la struttura del database.
  • thehackingquest: il nome del database che stiamo creando.
  • ; : delimitatore standard utilizzato per segnalare la fine di un comando SQL. Non tutti i sistemi richiedono un punto e virgola, ma è buona pratica includerlo quando eseguiamo più comandi SQL in sequenza.

Per cancellarlo, invece, utilizzeremo il comando:

DROP DATABASE thehackingquest;
  • DROP DATABASE: sintassi chiave del linguaggio SQL. Il comando “DROP” indica al sistema di gestione del database di eliminare un oggetto specifico. In questo caso, l’oggetto da eliminare è un “DATABASE”.
  • thehackingquest: nome del database che stiamo eliminando.
  • ; : come visto precedentemente, il punto e virgola è il delimitatore standard, utilizzato per segnalare la fine del comando.

Introduciamo adesso il concetto di tabella:

Immaginiamo di avere un quaderno e al suo interno creiamo una griglia o una tabella per organizzare alcune informazioni. Ogni riga, che prende il nome di tupla, rappresenta un singolo elemento o oggetto, e ogni colonna, che prende il nome di attributo, fornisce dettagli specifici su quell’elemento.

Righe – Tuple: Ogni riga, in una tabella di un database, rappresenta un singolo record o elemento.
Ad esempio, in una tabella chiamata “Studenti”, ogni riga rappresenterebbe un singolo studente.

Colonne – Attributi: Ogni colonna fornisce un tipo di informazione specifico sull’elemento.
Ad esempio, nella tabella “Studenti”, avremo colonne come “Nome”, “Cognome”, “Matricola”, ecc.

Un esempio semplificato:

Matricola Nome Cognome
001 Marco Rossi
002 Lucia Bianchi
003 Stefano Verdi

.

In questo esempio, la tabella “Studenti” ha tre colonne (attributi): “Matricola”, “Nome” e “Cognome”. Ci sono tre studenti (o righe/tuple) nella tabella, ognuno con dettagli univoci.

Dopo aver organizzato i dati in maniera ordinata ed averne scelto la logica di archiviazione, il nostro fine ultimo sarà quello di voler consultare (o manipolare) i dati presenti in tabella. Il modo per richiedere specifiche informazioni o per eseguire determinate operazioni sui dati memorizzati nel database, prende il nome di query. In altre parole, una query è una domanda che poniamo al database.

La query base per la manipolazione del database è la SELECT. La sintassi è la seguente:

SELECT colonna1, colonna2, colonnaN FROM tabella

Facciamo un esempio:

SELECT * FROM studenti;
  1. SELECT: indica al database di selezionare (o recuperare) dati. In altre parole, chiediamo al database di mostrarci “alcune” informazioni.
  2. *: L’asterisco (*) è un carattere che rappresenta “tutto”. Stiamo chiedendo al database di mostrarci tutte le colonne disponibili nella tabella specificata.
  3. FROM: specifichiamo da quale tabella del database vogliamo recuperare i dati.
  4. studenti: è il nome della tabella da cui recuperare i dati.
  5. ;: il punto e virgola chiude il comando.

Se invece volessimo visualizzare esclusivamente una specifica colonna, potremmo utilizzare la seguente query:

SELECT name, cognome FROM studenti;

La sintassi del comando è analoga a quella precedente, con l’unica differenza che abbiamo specificato due attributi (colonne): quella del nome e quella del cognome.

Facciamo un ulteriore passo. Ipotizziamo di voler selezionare dall’intera tabella “studenti” (quindi vogliamo che mostri tutte le righe e tutte le colonne) solamente quelli che hanno il cognome “Bianchi”. Per fare ciò, digiteremo il seguente comando:

SELECT * FROM studenti WHERE cognome=’Bianchi’;

Il comando WHERE, difatti è utilizzato per filtrare tutti i risultati in base alla condizione specificata.

Find SQL Injection

Una volta compresi i rudimenti della sintassi SQL, spostiamoci ad analizzare alcuni dei potenziali metodi, efficaci per trovare vulnerabilità di SQL injection.

La vulnerabilità si verifica quando un attaccante è in grado di inserire o “iniettare” istruzioni SQL, attraverso l’input fornito dall’applicazione, che verranno successivamente eseguite dal backend del database.

Per identificare un campo soggetto a questa vulnerabilità, possiamo sfruttare vari metodi:

  1. Risposte errate: se inserendo caratteri speciali come in un campo di input (ad esempio, un campo di ricerca o un login) l’applicazione restituisce un errore relativo al database, potrebbe essere il segnale di una potenziale vulnerabilità.
  2. Analisi dei comportamenti: inserendo istruzioni SQL in input e osservando come l’applicazione reagisce, un attaccante può determinare se sarebbe possibile influenzare la logica delle query SQL.
    Per esempio, inserire OR ‘1’=’1′ in un campo password potrebbe consentire l’accesso non autorizzato, se l’applicazione risulta vulnerabile.
  3. Time-based: tramite l’invio di codice specifico, l’attaccante induce il database ad “attendere” per un determinato periodo di tempo, confermando la vulnerabilità se l’applicazione ritarda la risposta di quel preciso intervallo.
  4. OAST (Out-of-band SQL Injection): l’attaccante inietta codici che costringono il database ad effettuare richieste di rete esterne (ad es. DNS o HTTP). Se l’attaccante riceve la richiesta sul suo server, ciò conferma l’efficacia dell’iniezione e la vulnerabilità dell’applicazione.

Passiamo adesso all’azione con degli esempi pratici, di difficoltà gradualmente crescente.
Per i nostri test utilizzeremo la piattaforma TryHackMe.

SQL Injection 1 – Input Box Non-String

LAB: https://tryhackme.com/room/sqlilab

Come prima cosa, avviamo l’istanza della macchina da attaccare. L’indirizzo dell’applicazione target sarà il seguente: http://10.10.211.107:5000

Inseriamo admin admin, nei campi “nome utente” e “password” e selezioniamo login. Come potevamo aspettarci, apparirà un messaggio di errore.

In basso, però, troviamo una preziosa informazione: la query che viene eseguita sul nostro database quando cerchiamo di effettuare il login.

SELECT uid, name, profileID, salary, passportNr, email, nickName, password FROM usertable WHERE profileID=admin AND password = '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918'

Dato che l’applicazione non effettua nessun controllo sull’input fornito, possiamo manipolare la query, bypassando la maschera di login e sfruttando una “True Condition” sul parametro profileID. Procediamo un passo alla volta.

Come prima cosa, analizziamo la query:

  • SELECT : indica al database da cui selezionare (o recuperare) dati. In altre parole, chiediamo al database di mostrarci “alcune” informazioni.
    Le informazioni che noi vogliamo visualizzare sono: uid, name, profileID, salary, passportNr, email, nickName, password e le vogliamo visualizzare dalla tabella che si chiama usertable.
  • WHERE : pone delle condizioni specifiche per filtrare i risultati. In questo caso, stiamo cercando le righe (o le voci) in cui il profileID è uguale a ‘admin‘ E (AND) la password è uguale all’hash fornito. Pertanto, la query restituirà solo le voci che soddisfano entrambe queste condizioni.

Dato che la condizione non è verificata, la richiesta non andrà a buon fine.

Per comprendere la logica dietro la SQL injection, facciamo un semplice esempio: immaginiamo un custode che ci permette di entrare solo se forniamo la password corretta. Il custode verificherà la nostra identità, confrontando nome e password che gli abbiamo fornito, con un elenco a sua disposizione. Se gli dicessimo qualcosa come: “Il mio nome è ‘jinwoo’ oppure (OR Booleano)” qualcosa che è sempre vero come “1=1” ed aggiungessimo “non considerare niente di ciò che ti diciamo dopo“, lui potrebbe confondersi e farci entrare senza verificare ulteriormente la password.

In termini tecnici, l’injection che faremo sarà la seguente:

'jinwoo' OR 1=1 --
  • ‘jinwoo’ : chiudiamo la stringa del nome utente (ovviamente il nome utente non esiste)
  • OR : l’operatore booleano OR verifica due condizioni, se almeno una di essa è vera, l’intera espressione risulta vera
  • 1=1 : la condizione ovvia, che è sempre vera
  • : viene usato in SQL per indicare l’inizio di un commento; quindi, stiamo dicendo al database di ignorare tutto ciò che viene dopo, perché si tratta solo di un commento (quindi non considera la verifica della password)
  • …e dato che l’applicazione non accetta NULL nel campo password, metteremo qualsiasi cosa in modo randomico.

Una cosa fondamentale da notare è che il parametro profileID accetta un numero come input. Ergo, abbiamo utilizzato un confronto numerico.

Ci siamo! A questo punto, quando cliccheremo su “login” otterremo la prima flag.

SQL Injection 2: Input Box String

La logica di questa seconda challenge è identica alla precedente, l’unica differenza è che questa volta il parametro profileID non si aspetta un valore numerico, ma una stringa. Quindi, andremo a modificare la nostra injection come segue:

jinwoo' or '1'='1' --

Come si può notare, l’unica differenza è stata quella di aver passato come stringhe (utilizzando gli apici) i valori di confronto. Dobbiamo inoltre togliere l’apice davanti al nome jinwoo, perché altrimenti la query risulterebbe malformata: il database restituirebbe un errore di sintassi, anziché eseguirla correttamente.

NOTA: Troncando la query potremmo anche injectare del codice in maniera arbitraria, ad esempio per reperire la versione del MySQL utilizzando (select @@version)

SQL Injection 3: UNION-based

UNION-based Payloads è una tecnica specifica, che sfrutta l’operatore SQL “UNION”. L’operatore UNION è utilizzato in SQL per combinare i risultati di due o più SELECT in un unico set di risultati.

NOTA: Addentriamoci in questa tecnica e spostiamoci in un altro laboratorio su TryHackMe a questo indirizzo: https://tryhackme.com/room/sqlinjectionlm

Per meglio comprenderlo, seguiamo assieme il semplice Walkthrough proposto.
Una volta avviata la macchina, ci troviamo difronte all’applicazione Web da attaccare:

L’applicazione da attaccare è un website che contiene vari articoli. Il seguente indirizzo URL naviga al primo articolo: https://website.thm/article?id=1

La query, che permette di visionare tale articolo, è una SELECT:

SELECT * FROM article WHERE id = 1

Abbiamo detto che per localizzare un SQL injection possiamo vedere come risponde l’applicazione, utilizzando una serie di caratteri.

Andiamo quindi nell’ URL e modifichiamo id=1 con id=’

Come possiamo notare dall’immagine sottostante, otterremo un errore. Tale circostanza ci conferma che una vulnerabilità SQL esiste.

Utilizziamo adesso l’operatore UNION, trasformando la query in:

SELECT * FROM article where id = 1 UNION SELECT 1

Ed analizziamola:

  • SELECT * FROM article WHERE id = 1 : questa prima parte della query seleziona tutti i campi (*) dalla tabella article dove l’id è uguale a 1
    · UNION : l’operatore UNION combina i risultati di due o più query SELECT in un unico set di risultati
    ·  SELECT 1: questa seconda parte della query seleziona semplicemente il valore 1

Attuiamo l’injection aggiungendo direttamente: https://website.thm/article?id=1 UNION SELECT 1

Perché l’attacco di SQL injection UNION funzioni, il numero di colonne selezionate in entrambe le query SELECT deve essere lo stesso. In questo caso, la prima query (SELECT * FROM article where id = 1) dovrebbe restituire un solo campo, affinché l’operatore UNION funzioni senza errori con la seconda query (SELECT 1). Se la tabella article avesse più di una colonna, l’uso diretto di UNION SELECT 1 genererebbe un errore.

Difatti, una volta eseguito il comando, riceviamo un nuovo errore:

L’errore ci sta suggerendo che abbiamo sbagliato il numero di colonne. Procedendo per tentativi, ci accorgiamo che il numero di colonne corretto è 3. Come capirlo? Semplicemente dal fatto che, una volta trovato l’esatto numero di colonne, non riceveremo più alcun errore.

select * from article where id = 1 UNION SELECT 1,2,3

Il walkthought di TryHackMe è sufficientemente esplicativo, quindi senza dilungarsi, vi basterà seguire i vari step.

Commentiamo assieme esclusivamente l’ultima query, che potrebbe risultare non proprio immediata ed è quella che permette di ottenere la password per risolvere l’esercizio.

Analizziamola:

select * from article where id = 0 UNION SELECT 1,2,group_concat(username,':',password SEPARATOR '') FROM staff_user
  1. select * from article where id = 0: è la query originale dell’applicazione, che ha l’obiettivo di selezionare un articolo basato sull’ID.
    L’ID originale dell’articolo era 1, abbiamo manipolato il valore affinché l’applicazione non visualizzasse direttamente articoli, ma lasciasse spazio alle nostre informazioni.
  2. UNION: è l’operatore utilizzato per unire i risultati di due set di query
  3. SELECT 1,2,group_concat(username,’:’,password SEPARATOR ”): questa è la seconda query (la parte iniettata), con cui chiediamo al database di selezionare tre valori:
    • Il numero 1
    • Il numero 2
    • La combinazione di username e password dalla tabella staff_user , utilizzando la funzione group_concat.
  4. group_concat(username,’:’,password SEPARATOR ”): la funzione group_concat in MySQL consente di concatenare i valori di più righe, in una singola stringa.
    Nello specifico caso, stiamo combinando i valori username e password con un carattere : tra di loro. Dal momento che il SEPARATOR specificato è una stringa vuota (”), non ci saranno spazi o altri caratteri tra le diverse coppie di username e password. Sarebbe come dire: “Elenca tutti gli username e le password, e separali con un’:’”.
  5. FROM staff_user: indica che vogliamo prelevare le informazioni dalla tabella chiamata staff_user.

SQL Injection 4: Blind SQL Injection

Nel “Blind SQL Injection” l’attaccante pone delle domande al database e, anche se non vede direttamente i dati, può carpire le informazioni basandosi sul corretto caricamento della pagina. Quindi, attraverso una serie di “domande” molto precise e l’osservazione delle risposte, l’attaccante può indovinare e scoprire le informazioni nel database, anche se non le vede direttamente.

Addentriamoci in questa tecnica attraverso la CTF SQHell di TryHackMe, che potete trovare al seguente indirizzo: https://tryhackme.com/room/sqhell

NOTA: lascio a voi la risoluzione per ottenere le varie FLAG. Ciò che a noi interessa, è uno scenario che ci aiuti a comprendere come funziona e viene eseguito l’attacco.

Prima di passare all’azione, dobbiamo fare una piccola digressione sul parametro X-Forwarded-For.

Quando un computer (chiamiamolo “A”) chiede informazioni a un altro computer (“B”) passando attraverso un intermediario (“C”), “C” potrebbe dire a “B”: “Hey, in realtà sto chiedendo queste informazioni per conto di “A”“. L’intermediario userà l’intestazione “X-Forwarded-For” per passare quest’informazione.

Facciamo adesso un esempio di utilizzo. Poniamo che l’indirizzo IP del sito internet da attaccare sia: http://10.10.187.62 .

Utilizzando il seguente comando, specifichiamo che l’indirizzo IP da cui proviene la richiesta originale è 127.0.0.1:

curl -H "X-Forwarded-For: 127.0.0.1" http://10.10.187.62

Analizziamolo in dettaglio:

  • curl: strumento da linea di comando per trasferire dati da/verso un server. È comunemente utilizzato per fare richieste HTTP, come quelle che farebbe un browser.
  • -H “X-Forwarded-For: 127.0.0.1”: questa opzione aggiunge un header (intestazione) alla richiesta HTTP. In questo caso, aggiungiamo l’header “X-Forwarded-For” con il valore “127.0.0.1”. Come già abbiamo detto, “X-Forwarded-For” viene spesso utilizzato dai proxy per indicare l’indirizzo IP originale da cui proviene la richiesta. Qui, stiamo fingendo che la nostra richiesta provenga dall’indirizzo IP “127.0.0.1”.
  • http://10.10.187.62: URL del server verso cui stiamo effettuando la richiesta

L’istanza di TryHackMe ci risponderà immediatamente con il codice HTML del sito.

Quindi, se un’applicazione web gestisce in modo improprio l’header X-Forwarded-For, potrebbe essere vulnerabile ad attacchi di SQL injection. In che modo?

  1. Utilizzo Diretto in SQL: se l’applicazione procede inserendo il valore dell’header direttamente in una query SQL, senza filtrarlo, un attaccante potrebbe manipolare l’header per alterare la query
  2. Interazione con Altre Funzioni: se l’header viene usato insieme ad altre funzionalità dell’applicazione, che interagiscono con un database, potrebbe esserci spazio per SQL Injection
  3. Blid Trust nell’Header: alcune applicazioni potrebbero fidarsi troppo dell’header X-Forwarded-For, pensando che provenga da fonti sicure. Senza controlli appropriati, un attaccante può introdurre valori malevoli attraverso questo header.

Ed è proprio ciò che accade in questa istanza di TryHackMe. Utilizzando l’header in causa, possiamo interagire direttamente con il database. Inoltre, per scoprire se la nostra interazione andrà a buon fine, possiamo utilizzare il “tempo di attesa” o “sleep time”. Vale a dire che, se l’attacco avrà successo, prima di visualizzare il codice HTML della pagina, il sito internet rimarrà in attesa il tempo specificato nel comando.

Il comando da utilizzare è il seguente:

curl -H "X-Forwarded-For: ' UNION SELECT SLEEP(10);--" http://10.10.187.62

Ma, come abbiamo visto precedentemente, perché l’operatore UNION funzioni il numero di colonne selezionato da ciascuna query deve essere lo stesso. Dobbiamo quindi modificarlo leggermente:

curl -H "X-Forwarded-For: ' UNION SELECT SLEEP(10),2,3;--" http://10.10.187.62

Adesso, quando lanceremo il comando, l’istanza web vulnerabile a questo attacco andrà in “sleep” per 10 secondi, prima di darci una risposta.

Questo tipo di attacco, basato sul tempo di risposta dell’istanza web, prende il nome di: Blind SQL Injection : Time-based.

Conclusioni

La SQL Injection è una delle tecniche di attacco più storiche e pericolose nel mondo della sicurezza informatica e continua ad essere estremamente rilevante, data la sua efficacia nell’ottenere accessi non autorizzati ai sistemi.

Mentre gli attaccanti si mostrano sempre più sofisticati nella strutturazione e nell’utilizzo delle tecniche, diventa fondamentale per gli sviluppatori e gli amministratori di sistema rimanere aggiornati sulle ultime minacce e metodi di protezione: implementando una codifica sicura, usando “prepared statements”, parametrizzando le query e conducendo regolari test di penetrazione.

In un mondo sempre più digitalizzato, la sicurezza delle informazioni non è più solo una considerazione tecnica, ma un dovere etico verso utenti e clienti. Prevenire attacchi come SQL Injection non riguarda più solo una questione di protezione dei dati, ma anche il mantenimento di una reputazione affidabile, in un ambiente come quello online che si dimostra sempre più vasto e profondo.

Go to Top