
L'azienda Variti sviluppa soluzioni di protezione da bot e attacchi DDoS, oltre a condurre test di stress e di carico. Alla conferenza HighLoad++ 2018 abbiamo parlato di come mettere in sicurezza le risorse da vari tipi di attacchi. In breve: isola le parti del sistema, utilizza servizi cloud e CDN e aggiorna regolarmente. Ma senza aziende specializzate nella protezione non ce la farete comunque 🙂
Prima di leggere il testo puoi consultare i brevi punti chiave .
E se non ami leggere o vuoi semplicemente guardare un video, la registrazione della nostra presentazione si trova qui sotto nel spoiler.
Registrazione video della presentazione

Molte aziende sono già in grado di effettuare test di carico, ma non tutte eseguono test di stress. Alcuni dei nostri clienti pensano che il loro sito sia invulnerabile perché hanno un sistema highload, e questo li protegge bene dagli attacchi. Noi dimostriamo però che non è del tutto vero.
Naturalmente, prima di eseguire i test otteniamo autorizzazione dal cliente, con firma e timbro, e con il nostro aiuto non è possibile effettuare un attacco DDoS su nessuno. I test vengono condotti nel momento scelto dal cliente, quando il traffico sul suo sito è minimo, evitando che eventuali problemi di accesso coinvolgano i clienti. Inoltre, poiché durante i test possono sempre verificarsi imprevisti, manteniamo un contatto costante con il cliente. Questo consente non solo di comunicare i risultati raggiunti, ma anche di apportare modifiche durante i test. Al termine dei test redigiamo sempre un rapporto in cui evidenziamo le vulnerabilità riscontrate e forniamo raccomandazioni su come affrontare i punti deboli del sito.
Come lavoriamo
Durante i test emuliamo un botnet. Poiché lavoriamo con clienti che non si trovano nelle nostre reti, affinché il test non si concluda nel primo minuto a causa dell'attivazione dei limiti o delle protezioni, applichiamo il carico non da un solo IP, ma dalla nostra sottorete. Inoltre, per generare un carico significativo disponiamo di un potente server di test.
Postulati
Molto non significa necessariamente bene
Meno carico ci vorrà per portare il servizio al collasso, meglio sarà. Se riusciamo a far sì che il sito smetta di funzionare con una richiesta al secondo, o addirittura con una richiesta al minuto, sarà fantastico. Questo perché, per legge di Murphy, gli utenti o i malintenzionati cadranno esattamente in questa vulnerabilità.
Un guasto parziale è meglio di uno totale
Consigliamo sempre di rendere i sistemi eterogenei. Inoltre, è importante separarli a livello fisico, e non solo attraverso la containerizzazione. In caso di separazione fisica, anche se qualcosa sul sito smette di funzionare, è molto probabile che non smetta di funzionare completamente, e gli utenti possano ancora accedere, almeno a una parte delle funzionalità.
Una corretta architettura è la base della resilienza
La resilienza di una risorsa e la sua capacità di resistere ad attacchi e carichi devono essere integrate fin dalla fase di progettazione, praticamente nella fase di creazione dei primi diagrammi a blocchi. Infatti, se si introducono errori fatali, può essere difficile correggerli successivamente.
Non deve essere solo il codice a essere buono, ma anche il configurazione
Molti pensano che un buon team di sviluppo garantisca la resilienza del servizio. Un buon team di sviluppo è sicuramente necessario, ma è altrettanto importante avere una buona gestione, un buon DevOps. Ossia, dobbiamo avere specialisti che configurino correttamente Linux e la rete, scrivano le configurazioni giuste in nginx, impostino limiti e altro. Altrimenti, la risorsa funzionerà bene solo in test, ma in produzione, a un certo punto, tutto si romperà.
Differenze tra test di carico e test di stress
Il test di carico consente di identificare i limiti di funzionamento del sistema. Il test di stress è finalizzato a identificare i punti deboli del sistema e viene utilizzato per cercare di romperlo e vedere come si comporta durante il collasso di alcune parti. A tal fine, il carico caratteristico rimane solitamente sconosciuto per il cliente fino all'inizio del test di stress.
Caratteristiche distintive degli attacchi L7
Di solito suddividiamo i tipi di carico in carichi a livello L7 e L3&4. L7 è il caricamento a livello applicativo, di solito si intende solo HTTP, noi invece intendiamo qualsiasi carico a livello di protocollo TCP.
Gli attacchi L7 hanno alcune caratteristiche distintive. Innanzitutto, arrivano direttamente all'applicazione, quindi è difficile rifletterli con mezzi di rete. Questi attacchi utilizzano la logica e, grazie a ciò, consumano in modo molto efficiente e con un traffico ridotto CPU, memoria, disco, database e altre risorse.
HTTP Flood
In caso di qualsiasi attacco, è più semplice generare un carico che gestirlo, e nel caso di L7 questo è altrettanto vero. Il traffico dell'attacco non è sempre facile da distinguere da quello legittimo e, nella maggior parte dei casi, si riesce a farlo in base alla frequenza, ma se tutto è pianificato in modo intelligente, è impossibile capire dai log dove ci sia un attacco e dove ci siano richieste legittime.
Come primo esempio, consideriamo l'attacco HTTP Flood. Dal grafico si può vedere che di solito questi attacchi sono molto potenti; nell'esempio qui sotto, il numero massimo di richieste ha superato le 600.000 al minuto.

HTTP Flood è il modo più semplice per generare un carico. Di solito, si utilizza uno strumento di testing del carico, come ApacheBench, e si impostano le richieste e l'obiettivo. Con questo approccio semplice, c'è una grande possibilità di imbattersi nella cache del server, ma è facile aggirarlo. Ad esempio, aggiungendo stringhe casuali nella richiesta, costringendo così il server a restituire continuamente una nuova pagina.
Non bisogna dimenticare l'user-agent durante la generazione del carico. Molti user-agent degli strumenti di test più popolari vengono filtrati dagli amministratori di sistema, e in tal caso il carico potrebbe semplicemente non arrivare al backend. È possibile migliorare notevolmente i risultati inserendo nella richiesta un'intestazione più o meno valida di un browser.
Nonostante la semplicità, gli attacchi HTTP Flood presentano anche i loro svantaggi. In primo luogo, per generare un carico sono necessarie grandi risorse. In secondo luogo, questi attacchi sono molto facili da rilevare, soprattutto se provengono da un singolo indirizzo. Di conseguenza, le richieste iniziano immediatamente a essere filtrate dagli amministratori di sistema o anche a livello del fornitore.
Cosa cercare
Per ridurre il numero di richieste al secondo e mantenere comunque l'efficienza, è necessario avere un po' di fantasia e analizzare il sito. Ad esempio, si può caricare non solo il canale o il server, ma anche singole parti dell'applicazione, come database o sistemi di file. Si possono anche cercare posti sul sito che effettuano calcoli complessi: calcolatrici, pagine di selezione dei prodotti e altro. Infine, spesso capita che ci sia uno script php sul sito che genera una pagina da centinaia di migliaia di righe. Questo script carica notevolmente il server e può diventare un obiettivo per attacchi.
Dove cercare
Quando scansioniamo una risorsa prima di effettuare un test, innanzitutto guardiamo, naturalmente, il sito stesso. Cerchiamo vari campi di input, file pesanti: insomma, tutto ciò che può creare problemi alla risorsa e rallentare il suo funzionamento. In questo ci aiutano gli strumenti di sviluppo banali di Google Chrome e Firefox, che mostrano i tempi di risposta della pagina.
Scansioniamo anche i sottodomini. Ad esempio, c'è un negozio online, abc.com, e ha un sottodominio admin.abc.com. Probabilmente si tratta di un'area admin con autenticazione, ma se la si sottopone a carico, potrebbe creare problemi per la risorsa principale.
Il sito potrebbe avere un sottodominio api.abc.com. Probabilmente è una risorsa per applicazioni mobili. L'app può essere trovata su App Store o Google Play, impostare un punto di accesso speciale, decodificare l'API e registrare account di test. Il problema è che spesso le persone pensano che tutto ciò che è protetto da autenticazione sia immune agli attacchi di negazione del servizio. Si crede che l'autenticazione sia la migliore CAPTCHA, ma non è così. Creare 10-20 account di test è semplice, e una volta creati, si ottiene accesso a funzionalità complesse e non protette.
Naturalmente, guardiamo la storia, il robots.txt e il WebArchive, ViewDNS, cercando vecchie versioni della risorsa. A volte capita che gli sviluppatori abbiano rilasciato, ad esempio, mail2.yandex.net, mentre la vecchia versione, mail.yandex.net, è rimasta. Questo mail.yandex.net smette di essere supportato, non vengono allocati risorse di sviluppo, ma continua a consumare il database. Di conseguenza, utilizzando la vecchia versione, si possono sfruttare efficacemente le risorse del backend e tutto ciò che sta dietro il layout. Certamente, ciò non accade sempre, ma ci troviamo comunque di fronte a situazioni simili piuttosto frequentemente.
Naturalmente, analizziamo tutti i parametri della richiesta e la struttura dei cookie. Possiamo, per esempio, inserire un valore in un array JSON all'interno dei cookie, creare un'elevata nidificazione e costringere la risorsa a lavorare irragionevolmente a lungo.
Carico della ricerca
La prima cosa che viene in mente quando si esplora un sito web è di sovraccaricare il database, poiché la ricerca è presente quasi ovunque e, purtroppo, è mal protetta nella maggior parte dei casi. Per qualche motivo, gli sviluppatori non prestano sufficiente attenzione alla ricerca. Tuttavia, c'è un consiglio: non dovresti fare richieste simili, poiché potresti incontrare caching, come nel caso di un attacco HTTP flood.
Effettuare richieste casuali al database non è sempre efficace. È molto meglio creare un elenco di parole chiave relative alla ricerca. Se torniamo all'esempio di un negozio online: supponiamo che il sito venda pneumatici per auto e permetta di impostare il raggio delle gomme, il tipo di auto e altri parametri. Di conseguenza, le combinazioni di parole pertinenti costringeranno il database a lavorare in condizioni molto più complesse.
Inoltre, è opportuno utilizzare la paginazione: è molto più difficile per la ricerca restituire la penultima pagina dei risultati rispetto alla prima. In altre parole, grazie alla paginazione puoi diversificare il carico.
Nell'esempio qui sotto mostriamo il carico sulla ricerca. Si vede che fin dai primi secondi del test, a una velocità di dieci richieste al secondo, il sito è andato in crash e non ha risposto.

Se non c'è ricerca?
Se non c'è ricerca, non significa che il sito non contenga altri campi di input vulnerabili. Un campo del genere potrebbe essere l'autenticazione. Al giorno d'oggi, gli sviluppatori amano creare hash complessi per proteggere il database delle credenziali dagli attacchi tramite tabelle arcobaleno. Questo è positivo, ma tali hash consumano molte risorse CPU. Un grande afflusso di false autenticazioni può portare a un 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 massiccio flood. A volte i siti accettano file allegati, incluso nel formato gzip. In tal caso, prendiamo un file di 1 TB, lo comprimiamo tramite 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 attualmente molto popolari come le Rest API. Proteggere una Rest API è molto più complicato rispetto a un sito web normale. Per le Rest API nemmeno i metodi banali di protezione contro attacchi di forza bruta e altre attività illegittime sono efficaci.
Le Rest API sono molto facili da compromettere, perché accedono direttamente al database. Questo comporta conseguenze piuttosto gravi per le aziende nel caso in cui il servizio venga interrotto. La questione è che le Rest API sono spesso collegate non solo al sito principale, ma anche all'app mobile e ad alcune risorse aziendali interne. Se tutto questo non funziona, l'impatto è molto più forte rispetto a un semplice sito web che smette di funzionare.
Carico su contenuti pesanti
Se ci viene proposto di testare un normale singolo sito web, un landing page, una pagina vetrina, che non dispone di funzionalità complesse, cerchiamo contenuti pesanti. Ad esempio, grandi immagini che il server restituisce, file binari, documentazione in pdf - cerchiamo di scaricare tutto ciò. Questi test caricano bene il sistema di file e saturano le linee di rete, rendendoli così efficaci. Anche se non si spegne il server durante il download di un grande file a bassa velocità, si saturerà semplicemente la linea del server target e si verificherà un'interruzione del servizio.
Nell'esempio di questo test si vede che a una velocità di 30 RPS il sito ha smesso di rispondere o ha restituito errori del server 500.

Non bisogna dimenticare anche la configurazione dei server. È comune incontrare persone che hanno acquistato una macchina virtuale, hanno installato Apache, hanno impostato tutto di default, e hanno posizionato un'applicazione PHP, e qui sotto è possibile vedere il risultato.

Qui il carico era diretto alla radice e ammontava a soli 10 RPS. Abbiamo atteso 5 minuti e il server è crollato. Non si sa esattamente perché sia crollato, ma si sospetta semplicemente che abbia esaurito la memoria e quindi ha smesso di rispondere.
Wave based
Negli ultimi uno o due anni sono diventati abbastanza popolari gli attacchi a onde. Questo è dovuto al fatto che molte organizzazioni acquistano vari dispositivi per proteggersi dagli attacchi DDoS, i quali richiedono un certo periodo di accumulo di statistiche per iniziare a filtrare l'attacco. In altre parole, non filtrano l'attacco nei primi 30-40 secondi, poiché stanno accumulando dati e imparando. Di conseguenza, in questi 30-40 secondi si possono inviare così tanti attacchi che il sito rimane giù per un lungo periodo, finché tutte le richieste non vengono elaborate.
Nel caso dell'attacco qui sotto, c'era un intervallo di 10 minuti, dopo il quale è arrivata una nuova ondata di attacco modificata.

Cioè, la protezione ha imparato, ha avviato la filtrazione, ma è arrivata una nuova ondata di attacco completamente diversa, e la protezione ha di nuovo iniziato a imparare. Di fatto, la filtrazione smette di funzionare, la protezione diventa inefficace e il sito non è accessibile.
Gli attacchi a onda si caratterizzano per valori molto alti al picco, possono raggiungere cento mila o milioni di richieste al secondo, nel caso dell'L7. Se parliamo dell'L3&4, lì possono esserci centinaia di gigabit di traffico, o, di conseguenza, centinaia di mpps, se calcolati in pacchetti.
Il problema di tali attacchi è la sincronizzazione. Gli attacchi provengono da un botnet, e per creare un picco molto grande in un colpo solo è necessaria un'alta sincronizzazione. Questa coordinazione non sempre funziona: a volte si verifica un picco parabolico che appare piuttosto misero.
Non di solo HTTP
Oltre a HTTP a livello L7, ci piace anche sfruttare altri protocolli. In genere, un normale sito web, specialmente un normale hosting, espone protocolli di posta e MySQL. I protocolli di posta sono meno soggetti a carichi rispetto ai database, ma possono anche essere sovraccaricati in modo abbastanza efficace, portando a un'CPU satura sul server.
Siamo riusciti a ottenere risultati utilizzando una vulnerabilità SSH del 2016. Attualmente questa vulnerabilità è corretta quasi ovunque, ma ciò non significa che non si possa caricare il SSH. Si può. Viene semplicemente inviata una enorme quantità di tentativi di autenticazione, il SSH consuma quasi tutta la CPU sul server e poi il sito web crolla già con uno o due richieste al secondo. Di conseguenza, queste uno o due richieste nei log non possono essere distinte da un carico legittimo.
Rimangono pertinenti anche numerose connessioni che apriamo nei server. In passato, Apache ha avuto questo problema, ora in realtà anche nginx lo presenta, poiché spesso viene configurato di default. Il numero di connessioni che nginx può mantenere aperte è limitato, quindi quando raggiungiamo questo numero, nginx non accetta più nuove connessioni e il sito non funziona.
Il nostro cluster di test dispone di CPU sufficienti per attaccare il handshake SSL. 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 visibilità di Google, sul ranking e sulla sicurezza. D'altra parte, purtroppo, SSL ha un problema con la CPU.
L3&4
Quando parliamo di attacchi ai livelli L3&4, parliamo generalmente di 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 sistemi di protezione risiede nel grande volume. La massima entità per L3&4 è stata di 1,5-2 Tbit/s. Tale traffico è molto difficile da gestire anche per le grandi aziende, inclusi Oracle e Google.
SYN e SYN-ACK sono i pacchetti utilizzati per stabilire una connessione. Pertanto, è difficile distinguere un SYN-flood dal carico legittimo: non è chiaro se si tratti di un SYN che è arrivato per stabilire una connessione o di parte di un flood.
UDP-flood
Di solito, i malintenzionati non hanno le risorse che abbiamo noi, quindi possono utilizzare l'amplificazione per organizzare attacchi. Vale a dire, il malintenzionato scansione di internet e trova server vulnerabili o configurati in modo errato, che ad esempio, in risposta a un pacchetto SYN, rispondono con tre SYN-ACK. Alterando l'indirizzo sorgente dall'indirizzo del server di destinazione, si può aumentare la potenza di un pacchetto, per esempio, di tre volte e indirizzare il traffico contro la vittima.

Il problema delle amplificazioni sta nella loro difficile individuazione. Tra gli ultimi esempi possiamo citare il noto caso del memcached vulnerabile. Inoltre, ora ci sono molti dispositivi IoT e telecamere IP, che sono principalmente configurati di default e, per di più, in modo errato, per cui attraverso tali dispositivi i malintenzionati effettuano attacchi nella maggior parte dei casi.

Non semplice SYN-flood
Il SYN-flood è senza dubbio il tipo di attacco più interessante dal punto di vista di un sviluppatore. Il problema è che spesso gli amministratori di sistema utilizzano il blocco degli IP per proteggersi. Tuttavia, non solo gli amministratori che agiscono secondo script ne risentono, ma purtroppo anche alcuni sistemi di protezione acquistati a caro prezzo.
Questo metodo può rivelarsi catastrofico, poiché se i malintenzionati sostituiscono Indirizzi IP, l'azienda bloccherà la propria sottorete. Quando il Firewall blocca il proprio cluster, ci saranno conseguenze negative per le interazioni esterne, e il servizio si guasterà.
Inoltre, non è difficile ottenere il blocco della propria rete. Se l'ufficio del cliente dispone di una rete Wi-Fi, oppure se la funzionalità delle risorse è monitorata attraverso vari strumenti, possiamo prendere l'indirizzo IP di questo sistema di monitoraggio o del cliente Wi-Fi e usarlo come fonte. Così, il servizio apparirà disponibile, ma gli indirizzi IP target saranno bloccati. Ad esempio, può essere bloccata la rete Wi-Fi della conferenza HighLoad, dove verrà presentato un nuovo prodotto dell'azienda, il che comporterebbe determinati costi commerciali ed economici.
Durante i test non possiamo utilizzare l'amplificazione tramite memcached con risorse esterne, poiché ci sono accordi per instradare il traffico solo verso indirizzi IP autorizzati. Pertanto, utilizziamo l'amplificazione tramite SYN e SYN-ACK, quando l'invio di un SYN provoca una risposta della macchina con due o tre SYN-ACK, moltiplicando così l'attacco da due a tre volte.
Strumenti
Uno dei principali strumenti che utilizziamo per il carico a livello L7 è Yandex-tank. In particolare, viene utilizzato un phantom come arma e ci sono diversi script per generare munizioni e analizzare i risultati.
Per analizzare il traffico di rete utilizziamo Tcpdump e per analizzare il server Nmap. Per generare carichi a livello L3&4 utilizziamo OpenSSL e un po' della nostra magia con la libreria DPDK. DPDK è una libreria di Intel che permette di lavorare con l'interfaccia di rete, bypassando lo stack Linux e aumentando 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 milioni di richieste al secondo da un'unica macchina.
Utilizziamo anche determinati generatori di traffico e strumenti speciali che sviluppiamo per test specifici. Ricordando la vulnerabilità sotto SSH, il set di strumenti sopra menzionato non può essere sfruttato. Se attacchiamo il protocollo di posta, utilizziamo strumenti di posta elettronica oppure semplicemente scriviamo script per questi.
Conclusioni
In conclusione, vorrei dire:
- Oltre ai tradizionali test di carico, è fondamentale eseguire anche test di stress. Abbiamo un esempio reale, quando un subappaltatore del partner ha effettuato solo test di carico. Questi hanno dimostrato che la risorsa reggeva il carico normale. Ma poi è emerso un carico atipico, i visitatori del sito hanno iniziato ad utilizzare la risorsa in un modo leggermente diverso, e alla fine il subappaltatore ha ceduto. Pertanto, è importante cercare vulnerabilità anche se siete già protetti da attacchi DDoS.
- È necessario isolare alcune parti del sistema dalle altre. Se avete un motore di ricerca, deve essere spostato su macchine separate, quindi nemmeno in Docker. Perché se il motore di ricerca o l'autenticazione falliscono, almeno qualcosa continuerà a funzionare. Nel caso di un negozio online, gli utenti continueranno a trovare i prodotti nel catalogo, a passare dall'aggregatore, ad acquistare se già autenticati, oppure ad autenticarsi tramite OAuth2.
- Non trascurate i vari servizi cloud.
- Utilizzate il CDN non solo per ottimizzare i ritardi di rete, ma anche come misura di protezione contro attacchi di esaurimento della banda e semplicemente flud in statica.
- È necessario utilizzare servizi di protezione specializzati. Non potete difendervi da attacchi L3&4 a livello di canale, perché probabilmente non avete una banda sufficiente. Anche le difese contro attacchi L7 sono improbabili, poiché possono essere molto grandi. Inoltre, la scoperta di piccoli attacchi è comunque prerogativa di servizi specializzati e algoritmi specifici.
- Aggiornate regolarmente. Questo riguarda non solo il core, ma anche il demone SSH, specialmente se sono esposti all'esterno. In linea di principio, bisogna aggiornare tutto, perché seguirne le vulnerabilità da soli è difficile.
Fonte: habr.com
