
L'azienda Variti sviluppa soluzioni di protezione da bot e attacchi DDoS, e conduce test di stress e di carico. Alla conferenza HighLoad++ 2018 abbiamo parlato di come proteggere le risorse da vari tipi di attacchi. In sintesi: isolate le parti del sistema, utilizzate servizi cloud e CDN e aggiornatevi regolarmente. Tuttavia, senza aziende specializzate nella protezione, non ce la farete comunque 🙂
Prima di leggere il testo, puoi consultare un breve riassunto .
E se non ti piace leggere o vuoi semplicemente guardare un video, la registrazione della nostra presentazione è qui sotto nel spoiler.
Registrazione video della presentazione

Molte aziende già effettuano test di carico, ma non tutte realizzano test di stress. Alcuni nostri clienti pensano che il loro sito sia invulnerabile perché possiedono un sistema highload, che offre una buona protezione dagli attacchi. Mostriamo invece che non è del tutto vero.
Naturalmente, prima di effettuare i test, otteniamo l'autorizzazione dal cliente, con firma e timbro; inoltre, non è possibile effettuare un attacco DDoS senza il nostro consenso. I test vengono eseguiti in un momento scelto dal cliente, quando il traffico del suo sito è minimo, in modo che i problemi di accesso non influiscano sui clienti. Inoltre, poiché durante i test può sempre verificarsi qualche imprevisto, siamo in costante contatto con il cliente. Questo permette non solo di comunicare i risultati ottenuti, ma anche di apportare modifiche durante il testing. Al termine dei test, redigiamo sempre un rapporto che evidenzia le eventuali problematiche riscontrate e forniamo raccomandazioni per eliminare le vulnerabilità del sito.
Come lavoriamo
Durante i test, emuliamo un botnet. Poiché collaboriamo con clienti che non sono all'interno delle nostre reti, per evitare che il test finisca nella prima istante a causa dell'attivazione di limiti o misure di protezione, inviamo il carico non da un solo IP, ma dalla nostra rete. Inoltre, per generare un carico significativo, abbiamo a disposizione un server di test piuttosto potente.
Postulati
Molto non significa necessariamente bene
Minore è il carico al quale possiamo far crollare la risorsa, meglio è. Se riusciamo a far sì che il sito smetta di funzionare con una richiesta al secondo, o addirittura con una richiesta al minuto, è perfetto. Perché, secondo la legge di Murphy, gli utenti o i malintenzionati inavvertitamente colpiranno proprio questa vulnerabilità.
Il guasto parziale è migliore del guasto totale
Consigliamo sempre di rendere i sistemi eterogenei. Inoltre, dovrebbero essere separati a livello fisico, non solo mediante containerizzazione. In caso di separazione fisica, anche se qualcosa nel sito smette di funzionare, c'è una buona probabilità che non smetta di funzionare completamente, e gli utenti manterranno l'accesso almeno a parte delle funzionalità.
Una buona architettura è la base della resilienza
La resilienza della risorsa e la sua capacità di resistere a attacchi e carichi devono essere integrate nella fase di progettazione, praticamente nel momento in cui si schizzano i primi diagrammi a blocchi nel taccuino. Perché se si introducono errori fatali, è possibile correggerli in seguito, ma è molto difficile.
Non solo il codice deve essere buono, ma anche la configurazione
Molti pensano che un buon team di sviluppo sia una garanzia di resilienza del servizio. Un buon team di sviluppo è certamente necessario, ma è altrettanto importante una buona gestione operativa, un buon DevOps. Ciò significa che sono necessari specialisti in grado di configurare correttamente Linux e la rete, scrivere adeguatamente le configurazioni in nginx, impostare limiti e altre impostazioni. Altrimenti, la risorsa funzionerà bene solo in fase di test, mentre in produzione, a un certo punto, tutto potrebbe rompersi.
Differenze tra test di carico e test di stress
Il test di carico permette di identificare i limiti di funzionamento del sistema. Il test di stress è volto a trovare i punti deboli del sistema e viene utilizzato per 'rompere' il sistema e osservare come si comporta durante il fallimento di determinate parti. Inoltre, il tipo di carico rimane solitamente sconosciuto al cliente fino all'inizio dei test di stress.
Caratteristiche distintive degli attacchi L7
Le tipologie di carico sono solitamente suddivise in carichi a livello L7 e L3&4. L7 si riferisce al carico a livello applicativo, che nella maggior parte dei casi è inteso solo per HTTP, mentre noi consideriamo qualsiasi carico a livello di protocollo TCP.
Gli attacchi L7 presentano alcune caratteristiche distintive. In primo luogo, arrivano direttamente all'applicazione, quindi è difficile contrastarli con strumenti di rete. Questi attacchi utilizzano la logica e, grazie a ciò, consumano in modo molto efficace CPU, memoria, disco, database e altre risorse anche con poco traffico.
HTTP Flood
In caso di qualsiasi attacco, è più facile generare il carico rispetto a gestirlo, e questo è vero anche per L7. Il traffico di attacco non sempre è facile da distinguere da quello legittimo, e spesso questo riesce a farlo in base alla frequenza, ma se tutto è pianificato con cura, nei log è impossibile capire dove ci sia un attacco e dove ci siano richieste legittime.
Come primo esempio, consideriamo un attacco HTTP Flood. Dal grafico si può notare che generalmente questi attacchi sono molto potenti; nell'esempio qui sotto, il numero massimo di richieste ha superato le 600.000 al minuto.

HTTP Flood è il metodo più semplice per generare carico. Di solito si utilizza uno strumento di stress test, come ApacheBench, e si impostano la richiesta e l'obiettivo. Con questo approccio semplice, c'è una grande probabilità di incappare nel caching del server, ma è facile aggirarlo. Ad esempio, aggiungendo stringhe casuali alla richiesta, si costringe il server a restituire continuamente una pagina fresca.
Non bisogna dimenticare il user-agent durante la creazione del carico. Molti user-agent dei popolari strumenti di test vengono filtrati dagli amministratori di sistema, e in tal caso il carico potrebbe semplicemente non arrivare al backend. È possibile migliorare significativamente il risultato inserendo nell'intestazione della richiesta un'intestazione di browser più o meno valida.
Nonostante la semplicità dell'attacco, gli attacchi HTTP Flood hanno anche i loro svantaggi. In primo luogo, per generare carico sono necessarie grandi risorse. In secondo luogo, tali attacchi vengono rilevati molto facilmente, soprattutto se provengono da un solo indirizzo. Di conseguenza, le richieste iniziano immediatamente a essere filtrate dagli amministratori di sistema o addirittura a livello del fornitore.
Cosa cercare
Per ridurre il numero di richieste al secondo senza compromettere l'efficienza, è necessario mostrare un po' di creatività e esplorare il sito. È possibile caricare non solo il canale o il server, ma anche parti specifiche dell'applicazione, come database o sistemi di file. Inoltre, è utile cercare aree del sito che effettuano calcoli pesanti: calcolatrici, pagine di selezione dei prodotti e altro ancora. Infine, spesso ci sono script PHP sul sito che generano pagine da centinaia di migliaia di righe. Questi script possono caricare significativamente il server e diventare un obiettivo per un attacco.
Dove cercare
Quando scansioniamo una risorsa prima di effettuare un test, guardiamo innanzitutto al sito stesso. Cerchiamo vari campi di input, file pesanti — insomma, tutto ciò che può creare problemi alla risorsa e rallentare il suo funzionamento. Qui aiutano i comuni strumenti di sviluppo in Google Chrome e Firefox, che mostrano i tempi di risposta della pagina.
Scansioniamo anche i sottodomini. Ad esempio, supponiamo che ci sia un negozio online, abc.com, e abbia un sottodominio admin.abc.com. Probabilmente si tratta di un'area di amministrazione con autenticazione, ma se viene sovraccaricata, potrebbe causare problemi al sito principale.
Il sito potrebbe avere un sottodominio api.abc.com. Probabilmente è una risorsa per applicazioni mobili. L'applicazione può essere trovata su App Store o Google Play, installando un punto di accesso speciale, analizzando l'API e registrando account di prova. Il problema è che spesso le persone pensano che tutto ciò che è protetto da autenticazione sia immune agli attacchi di negazione di servizio. Si crede erroneamente che l'autenticazione sia la migliore CAPTCHA, ma non è così. Creare 10-20 account di prova è semplice, e una volta creati, abbiamo accesso a funzionalità complesse e non schermate.
Naturalmente, analizziamo la storia, il robots.txt e WebArchive, ViewDNS, cercando vecchie versioni della risorsa. A volte capita che gli sviluppatori rilascino, ad esempio, mail2.yandex.net, mentre una vecchia versione, mail.yandex.net, rimane attiva. Questo mail.yandex.net non viene più supportato, non riceve risorse di sviluppo, ma continua a consumare il database. Di conseguenza, utilizzando la vecchia versione, possiamo sfruttare in modo efficace le risorse del backend e tutto ciò che supporta il rendering. Certamente, non accade sempre, ma ci imbattiamo comunque abbastanza spesso in situazioni simili.
Naturalmente, esaminiamo tutti i parametri della richiesta, la struttura dei cookie. Possiamo, ad esempio, inserire un valore all'interno di un array JSON nei cookie, creare una grande nidificazione e costringere la risorsa a funzionare in modo irragionevolmente lento.
Carico della ricerca
La prima cosa che viene in mente quando si esplora un sito è caricare il database, poiché la ricerca è presente nella maggior parte dei casi, e spesso è purtroppo poco protetta. Per qualche motivo, gli sviluppatori non attribuiscono sufficiente attenzione alla ricerca. Tuttavia, c'è un consiglio: evitare di effettuare richieste monotone, poiché si può incorrere nel problema della cache, come nel caso di un attacco HTTP flood.
Effettuare richieste casuali al database non è sempre efficace. È molto meglio creare un elenco di parole chiave pertinenti alla ricerca. Ritornando all'esempio di un negozio online: supponiamo che il sito venda pneumatici per auto e consenta di specificare il raggio delle gomme, il tipo di auto e altri parametri. Pertanto, combinazioni di parole rilevanti costringeranno il database a lavorare in condizioni molto più complesse.
Inoltre, è utile utilizzare la paginazione: per la ricerca è molto più difficile restituire la penultima pagina dei risultati rispetto alla prima. Quindi, attraverso la paginazione, è possibile diversificare un po' il carico.
Nell'esempio sottostante, dimostriamo il carico sulla ricerca. Si osserva che fin dal primo secondo del test, con una velocità di dieci richieste al secondo, il sito ha smesso di rispondere.

Se non ci sono risultati nella ricerca?
Se non ci sono risultati, non significa che il sito non contenga altri campi vulnerabili. Un campo del genere potrebbe essere l'autenticazione. Oggi gli sviluppatori amano utilizzare hash complessi per proteggere il database delle credenziali dagli attacchi tramite tabelle arcobaleno. È un buon approccio, ma tali hash richiedono grandi risorse CPU. Un elevato volume di tentativi di accesso non validi porta al sovraccarico della CPU e, di conseguenza, il sito smette di funzionare.
La presenza di vari moduli per commenti e feedback sul sito è un motivo per inviare testi molto lunghi o semplicemente creare un flooding massiccio. A volte i siti accettano file allegati, anche in formato gzip. In tal caso, prendiamo un file di dimensione 1TB, lo comprimiamo con gzip fino a pochi byte o kilobyte e lo inviamo al sito. Successivamente, viene decompresso e si ottiene un effetto molto interessante.
Rest API
Vorrei dedicare un po' di attenzione a servizi molto popolari al giorno d'oggi come Rest API. Proteggere un Rest API è molto più difficile rispetto a un sito web normale. Per i Rest API non funzionano nemmeno i banali metodi di protezione contro l'attacco a forza bruta e altre attività illecite.
Le REST API è molto facile da compromettere, poiché si connette direttamente al database. Un malfunzionamento di questo servizio può avere conseguenze piuttosto gravi per l'attività. Infatti, il REST API è spesso legato non solo al sito principale, ma anche all'app mobile e ad alcune risorse aziendali interne. Se tutto questo smette di funzionare, l'impatto è molto più forte rispetto a un semplice sito web che si guasta.
Carico su contenuti pesanti
Quando ci viene proposto di testare un'applicazione semplice, una landing page o un sito vetrina senza funzionalità complesse, cerchiamo contenuti pesanti. Ad esempio, immagini di grandi dimensioni fornite dal server, file binari, documentazione PDF: proviamo a scaricare tutto questo. Test del genere mettono a dura prova il sistema di file e saturano i canali, risultando quindi efficaci. Anche se non si va a sovraccaricare il server scaricando un grande file a bassa velocità, si saturerà comunque il canale del server target, causando un rifiuto nel servizio.
Da questo test si può notare che a una velocità di 30 RPS il sito ha smesso di rispondere o restituiva errori 500 del server.

Non dimentichiamo la configurazione dei server. Spesso ci si imbatte in persone che acquistano una virtual machine, installano Apache, configurano tutto con le impostazioni predefinite e posizionano un'applicazione php, e di seguito si può vedere il risultato.

Qui il carico era concentrato all'origine e si attestava a soli 10 RPS. Abbiamo atteso 5 minuti, e il server si è bloccato. Non si sa esattamente perché sia successo, ma si ipotizza che abbia semplicemente esaurito la memoria, smettendo quindi di rispondere.
Wave based
Negli ultimi uno o due anni, gli attacchi a onde sono diventati abbastanza popolari. Questo è dovuto al fatto che molte organizzazioni acquistano vari hardware per proteggersi dagli attacchi DDoS, che richiedono un certo tempo per accumulare statistiche prima di iniziare a filtrare l'attacco. Ciò significa che non filtrano l'attacco nei primi 30-40 secondi, mentre raccolgono dati e si affinano. Di conseguenza, in quei 30-40 secondi, si può inondare il sito con richieste tali da farlo collassare per un lungo periodo, fino a quando tutte le richieste non vengono gestite.
Nel caso dell'attacco sottostante, c'è stato un intervallo di 10 minuti, dopo il quale è arrivata una nuova e modificata ondata di attacco.

Cioè, la protezione si è addestrata, ha avviato la filtrazione, ma è arrivato un nuovo attacco completamente diverso, e la protezione ha ricominciato l'addestramento. Di fatto, la filtrazione smette di funzionare, la protezione diventa inefficace e il sito non è accessibile.
Gli attacchi a onda presentano valori molto elevati al picco, possono raggiungere centomila o addirittura un milione di richieste al secondo, nel caso dell'L7. Per quanto riguarda l'L3&4, ci possono essere centinaia di gigabit di traffico, o, rispettivamente, centinaia di mpps, se consideriamo i pacchetti.
Il problema di tali attacchi è la sincronizzazione. Gli attacchi provengono da una botnet, e per creare un picco singolo molto elevato, è necessaria un'alta sincronizzazione. E questa coordinazione non sempre riesce: a volte il risultato è un picco parabolico, che appare piuttosto misero.
Non di solo HTTP si vive
Oltre all'HTTP a livello L7, ci piace sfruttare anche altri protocolli. In genere, un normale sito web, specialmente un comune hosting, espone protocolli di posta e MySQL. I protocolli di posta sono meno soggetti a sovraccarichi rispetto ai database, ma possono comunque essere caricati in modo sufficientemente efficace, causando un sovraccarico della CPU sul server.
Abbiamo effettivamente avuto successo sfruttando la vulnerabilità SSH del 2016. Ora questa vulnerabilità è stata corretta dalla maggior parte, ma ciò non significa che non si possa generare carico su SSH. Si può fare. Viene semplicemente generato un enorme carico di autorizzazioni, e SSH consuma quasi tutta la CPU del server, portando il sito web a collassare con solo uno o due richieste al secondo. Di conseguenza, queste uno o due richieste non possono essere distinte nei log da un carico legittimo.
Rimangono rilevanti anche molti collegamenti che apriamo nei server. In passato, questo era un problema con Apache, ora affligge praticamente anche nginx, poiché spesso viene configurato di default. Il numero di connessioni che nginx può mantenere aperte è limitato; di conseguenza, una volta raggiunto questo numero, nginx non accetta più nuove connessioni, e il sito web smette di funzionare.
Il nostro cluster di test ha una potenza di CPU sufficiente per attaccare il SSL handshake. In linea di principio, come dimostra la pratica, anche i botnet a volte amano farlo. Da un lato, è chiaro che non si può fare a meno di SSL, poiché influisce sulla classifica di Google e sulla sicurezza. D'altra parte, sfortunatamente, SSL ha un problema di CPU.
L3&4
Quando parliamo di attacchi ai livelli L3&4, ci riferiamo generalmente ad attacchi a livello di canale. Questo tipo di carico è quasi sempre distinguibile da quello legittimo, a meno che non si tratti di un attacco SYN-flood. Il problema degli attacchi SYN-flood per i mezzi di protezione risiede nel loro elevato volume. La massima dimensione per L3&4 ha raggiunto i 1,5-2 Tbps. Tale traffico è molto difficile da gestire anche per grandi aziende, compresi Oracle e Google.
SYN e SYN-ACK sono pacchetti utilizzati per l'instaurazione di una connessione. Pertanto, è difficile distinguere un attacco SYN-flood da un carico legittimo: non è chiaro se sia un SYN ricevuto per stabilire una connessione o parte di un flood.
UDP-flood
Di solito, gli attaccanti non dispongono della potenza di fuoco che abbiamo noi, quindi per eseguire attacchi possono sfruttare l'amplificazione. In altre parole, un attaccante scansiona Internet e trova server vulnerabili o configurati in modo errato, che, ad esempio, rispondono con tre SYN-ACK a un solo pacchetto SYN. Fingendo l'indirizzo sorgente dell'indirizzo del server obiettivo, è possibile aumentare la potenza di un pacchetto, ad esempio, di tre volte e reindirizzare il traffico sulla vittima.

Il problema delle amplificazioni risiede nella loro difficile individuazione. Tra gli ultimi esempi, si ricorda il caso noto relativo a memcached vulnerabile. Inoltre, ora ci sono molti dispositivi IoT e telecamere IP che, per lo più, sono configurati di default in modo errato, quindi gli attaccanti li utilizzano frequentemente per effettuare attacchi.

SYN-flood complesso
L'attacco SYN-flood è probabilmente il più interessante di tutti per un sviluppatore. Il problema è che spesso gli amministratori di sistema utilizzano il blocco degli indirizzi IP per proteggersi. Purtroppo, non solo gli amministratori che seguono script ne risentono, ma anche alcuni sistemi di sicurezza costosi.
Questo metodo può rivelarsi catastrofico, poiché se i malintenzionati sostituiscono Indirizzi IP, l'azienda bloccherà la propria subnet. Quando il Firewall blocca il proprio cluster, le interazioni esterne si interrompono e il servizio va in down.
In effetti, ottenere il blocco della propria rete non è difficile. Se nel ufficio del cliente è presente una rete Wi-Fi, oppure se l’efficacia dei servizi viene monitorata tramite diversi strumenti, prendiamo l'indirizzo IP di questo sistema di monitoraggio o quello della rete Wi-Fi dell'ufficio e lo utilizziamo come sorgente. In apparenza, il servizio è accessibile, ma gli indirizzi IP target sono bloccati. Potrebbe anche essere bloccata la rete Wi-Fi della conferenza HighLoad, dove viene presentato un nuovo prodotto dell'azienda, il che comporta determinati costi aziendali ed economici.
Durante il test, non possiamo utilizzare l'amplificazione tramite memcached con risorse esterne a causa di accordi che limitano il traffico solo agli indirizzi IP autorizzati. Pertanto, utilizziamo l'amplificazione tramite SYN e SYN-ACK, quando l'invio di un SYN provoca la risposta di due o tre SYN-ACK, aumentando così l'attacco di due o tre volte.
Strumenti
Uno dei principali strumenti che utilizziamo per il carico a livello L7 è Yandex-tank. In particolare, come artiglieria utilizziamo Phantom, e ci sono anche diversi script per generare munizioni e analizzare i risultati.
Per analizzare il traffico di rete, utilizziamo Tcpdump; per analizzare il server, utilizziamo Nmap. Per generare carico a livello L3&4, utilizziamo OpenSSL e un po' della nostra magia con la libreria DPDK. DPDK è una libreria di Intel che consente di lavorare con l'interfaccia di rete, bypassando lo stack Linux, migliorando così l'efficienza. Naturalmente, utilizziamo DPDK non solo a livello L3&4, ma anche a livello L7, perché consente di generare un flusso di carico molto elevato, nell'ordine di diversi milioni di richieste al secondo da una sola macchina.
Utilizziamo anche generatori di traffico specifici e strumenti creati per test specifici. Se consideriamo la vulnerabilità sotto SSH, il set di strumenti sopra menzionato non può essere sfruttato. Se attacchiamo il protocollo di posta, utilizziamo utility di posta o semplicemente scriviamo script per esse.
Conclusioni
In conclusione, vorrei dire:
- Oltre al classico testing del carico, è fondamentale effettuare anche test di stress. Abbiamo un esempio reale in cui un subappaltatore del partner ha eseguito solo il test di carico. Questo ha dimostrato che la risorsa supporta il carico normale. Ma poi è comparso un carico anomalo, i visitatori del sito hanno iniziato a utilizzare la risorsa in modo diverso, e alla fine il subappaltatore è andato in down. Pertanto, è importante cercare vulnerabilità anche se siete già protetti dagli attacchi DDoS.
- È necessario isolare alcune parti del sistema da altre. Se hai un motore di ricerca, deve essere ospitato su macchine separate, non all'interno di Docker. Questo perché, se il motore di ricerca o l'autenticazione dovessero fallire, almeno qualcos'altro continuerebbe a funzionare. Nel caso di un negozio online, gli utenti continueranno a trovare prodotti nel catalogo, navigare dai comparatori, acquistare se già autenticati o autenticarsi tramite OAuth2.
- Non trascurare i vari servizi cloud disponibili.
- Utilizza il CDN non solo per ottimizzare i ritardi di rete, ma anche come mezzo di protezione contro gli attacchi di esaurimento del canale e semplicemente per il flood sui contenuti statici.
- È necessario utilizzare servizi di protezione specializzati. Non puoi proteggerti da attacchi L3&4 a livello di canale, perché probabilmente non hai una banda sufficiente. Inoltre, gli attacchi L7 sono difficili da respingere, poiché possono essere molto grandi. Inoltre, identificare attacchi minori è compito di servizi specializzati e algoritmi dedicati.
- Aggiornati regolarmente. Questo vale non solo per il kernel, ma anche per il daemon SSH, soprattutto se sono esposti all'esterno. Fondamentalmente, è necessario aggiornare tutto, perché è difficile monitorare autonomamente determinate vulnerabilità.
Fonte: habr.com
