Periodicamente mi occupo di verificare il log di alcuni server per analizzare eventuali operazioni non previste e non autorizzate.
Ci sono numerosi tool che già svolgono un monitoraggio attivo e segnalano o bloccano preventivamente tali azioni. Li uso, li ritengo strumenti indispensabili per il mio lavoro, ma almeno una volta al mese, faccio dei controlli manuali.
Le considerazioni di base sono che:
- i server ai quali ho accesso sono tutti server con distribuzione Linux/Unix like
- dispongo di credenziali per l’accesso ssh
- e l’utente con cui accedo è autorizzato a consultare la cartella dei log di nginx.
Premetto che i comandi che riporterò di seguito possono essere adattati per personalizzare la consultazione di qualsiasi file di log, quindi sono da prendere come spunto.
L’access.log di Nginx
Il file access.log di nginx è un file di testo che si presenta con la seguente struttura per ogni riga:
192.168.0.2 - - [28/Sep/2024:00:00:08 +0200] "POST /hello.php HTTP/1.1" 301 187 "-" "-"
Le informazioni sono organizzate in colonne separate da un carattere di spazio e sono:
- Indirizzo IP del client che ha chiesto la risorsa (192.168.0.2)
- il carattere di separatore
-
- Utente remoto oppure “-” per richieste anonime.
- Data e ora nel formato [DD/MMM/YYYY:HH:mm:ss +TZ].
- Richiesta costituita a sua volta da 3 informazioni importanti:
- il metodo della richiesta (POST, GET, PUT, HEAD)
- la URI richiamata comprensiva quindi di querystring
- Il protocollo di comunicazione (HTTP/1.1)
- Il codice di risposta (301)
- la dimensione del corpo della request (187)
- Il referer, se disponibile, altrimenti “-“
- Lo user agent, se fornito dal browser, altrimenti “-“
Consultare queste righe di log da riga di comando spesso diventa molto complicato a causa della leggibilità di queste informazioni.
Inoltre diventa complicato riuscire a seguire il flusso di navigazione che ciascun indirizzo IP ha seguito per raggiungere specifici target.
Per aiutarmi in questo processo, come ho scritto all’inizio, mi avvalgo di strumenti che migliorano la consultazione di questi file, ma successivamente, affezionato alla cara buona vecchia scuola, accedo da terminale al server (solitamente via ssh) e inizio a digitare (qualche volta mi è capitato di sentire familiari e amici dirmi: “sembra di vedere le scene di Matrix”, sai, quelle con tutti i caratteri che scendono dall’alto).
Strumenti da riga di comando
In questo articolo descriverò, limitatamente all’esigenza del momento i seguenti comandi:
- grep
- awk
- sort
Suggerisco di vedere la documentazione completa del comando, digitando man comando
da riga di terminale poichè non si limitano a fare solo quello che ho riportato in questo articolo.
Il comando grep
Questo comando ricerca uno specifico pattern in ogni file fornito in input.
Vuol dire che estrarrà solo le righe che corrisponderanno ad una specifica sequenza di caratteri o regola basata su espressione regolare.
È il primo comando della catena, poichè mi aiuta a scremare il file concentrandomi su uno specifico arco temporale.
Per esempio, se volessi consultare tutte le operazioni svolte dalle 08:00:00 alle 08:59:59, basterà eseguire questo comando:
grep "2024:08:" /var/log/nginx/access.log
Ciò vuol dire che estrarrà dal file access.log le sole righe che combaciano con la sottostringa di ricerca “:2024:08:” (posso renderla anche più accurata definendo una regola più puntuale, ma è inutile ai fini dell’articolo stesso).
Quando ci si trova al cospetto di un sito con centinaia, migliaia o decine di migliaia di vistite all’ora, solo questo comando non è utile, quindi tendo ad escludere una serie user agent che ritengo attendibili (per esempio UptimeRobot).
Sfruttando la concatenazione prevista dalla sintassi della CLI di Linux accoderò al comando precedente un ulteriore grep.
grep "2024:08:" /var/log/nginx/access.log \
| grep -v "UptimeRobot"
Il flag “-v” nel comando indica che deve estrapolare le sole righe che non contengono il pattern indicato.
Il carattere “|” concatena i due comandi, dirottando l’output del primo comando come input del secondo.
A questo punto ho un primo filtro sull’elenco. Posso concatenare più operazioni del genere per scremare ulteriormente l’elenco. Tuttavia il risultato sarà sempre lo scroll di matrix con siti ad alto traffico e le righe di output salteranno avanti e indietro da una riga all’altra presentando un risultato abbastanza disordinato.
Il comando awk
Il comando awk
in realtà è un alias del comando mawk
. AWK in realtà è un linguaggio di programmazione. Considerando che in questo articolo non spiegherò i dettagli di tale linguaggio, farò riferimento a mawk
sempre come awk
(impropriamente, ma consuetudine di tutti i DevOps/Sistemisti… almeno di tutti quelli che ho conosciuto).
awk
è un comando che permette la scansione e la manipolazione del testo in un testo.
Come detto all’inizio di questo articolo, il file di log di nginx è separato da spazi, quindi volendo estrapolare l’IP del client (colonna 1), la data (colonna 3) e la richiesta (colonna 5) potrei accodare alle istruzioni già fornite il comando che segue:
awk '{print $1 "\t" $3 "\t" $5}'
Quindi il comando completo risulterà pressoché così:
grep "2024:08:" /var/log/nginx/access.log \
| grep -v "UptimeRobot" \
| awk '{print $1 "\t" $4 "\t" $5}'
Ciò significa che processerà tutte le righe del file e prenderà le colonne 1, 3 e 5 ($1, $3, $5) e le separerà con un carattere di tabulazione (\t).
Ma già leggo la delusione nei tuoi occhi, quello che vedi a schermo è qualcosa del genere.
162.158.129.159 [28/Sep/2024:08:59:57 +0200]
Il comando ha funzionato alla perfezione, sono stato io a non formulare correttamente la sintassi.
awk di default utilizza lo spazio come carattere di separazione, quindi dobbiamo rendere il comando più complicato sfruttando l’argomento -F del comando.
-F permette di specificare il separatore da utilizzare. Nota importante, ammette un’espressione regolare come regola. Quindi utilizzerò una semplice espressione regolare (estremamente semplificata), per dividere in meglio in colonne, che si presenterà così: [ "\[\]]+
ovvero il separatore è una qualsiasi sequenza di caratteri di spazi, doppi apici, parentesi quadre aperte e chiuse (occhio a questa ultima condizione, potrebbe rompere il risultato finale, dovrai trovare tu la regola più adatta all’analisi dei tuoi log).
Il comando quindi si trasformerà nella forma che vedi sotto.
grep "2024:08:" /var/log/nginx/access.log \
| grep -v "UptimeRobot" \
| awk -F '[ "\[\]]+' \
'{print $1 "\t" $4 "\t" $7}'
Gli indici di riferimento sono cambiati rispetto a quelli indicati in testa a questo articolo, consequenzialmente al cambio della regola specificata.
Il risultato ottenuto quindi sarà qualcosa del genere:
172.69.130.170 28/Sep/2024:08:00:03 /hello.html
Avremo quindi una visualizzazione più compatta ed essenziale.
Per ottenere ulteriori dettagli (es. il metodo POST/GET e la dimensione della richiesta) sarà sufficiente modificare l’ultima parte del comando.
grep "2024:08:" /var/log/nginx/access.log \
| grep -v "UptimeRobot" \
| awk -F '[ "\[\]]+' \
'{print "\t" $4 "\t" $6 "\t" $10 "\t" $7}'
Come ho già scritto più volte, la regola di separazione e le colonne da visualizzare sceglile in base alle tue specifiche esigenze.
Il comando sort
Solitamente prima di svolgere un’analisi del traffico di questo tipo, mi estraggo tutti gli indirizzi IP univoci dal log (nella fascia indicata) e controllo se qualcuno di essi ha una provenienza sospetta.
Per estrarre gli indirizzi IP unici faccio uso del comando sort con l’attivazione del flag --unique
.
Il comando usato per estrapolare dal log solo gli indirizzi IP è riportato di seguito.
grep "2024:08:" /var/log/nginx/access.log \
| awk '{print $1}' \
| sort --unique
Infine la lista restituita in output, la do in pasto ad uno script che mi sono prodotto che utilizza un servizio premium per l’analisi degli indirizzi IP, indicandomi quali sono quelli potenzialmente pericolosi.
Volendo puoi utilizzare numerosi strumenti on-line per ottenere queste informazioni. Non mi riferisco a nessuno in particolare poichè questo articolo non intende promuoverne o avvantaggiarne nessuno.
Escludere specifiche estensioni dai risultati
Sei arrivato alla conclusione di questo articolo e come bonus per la caparbietà dimostrata fino ad ora, ecco un aiuto a scremare i risultati ottenuti.
Metti caso tu voglia escludere dall’elenco generato gli asset statici del sito (immagini, fogli di stile, file javascript), puoi accodare questo comando ai comandi sopra:
grep -vE "\.(jpg|js|css)(\?.*)?$"
Il flag -v già è stato spiegato con il comando grep più sopra, mentre il flag -E indica che bisogna interpretare il pattern come un’espressione regolare estesa.
L’espressione si può leggere qualsiasi elemento dell’elenco che finisce con estensione jpg, js o css ed eventualmente seguito da una querystring. Il flag -v applica una negazione, quindi fa esattamente il contrario, estraendo le righe che non corrispondono a questo pattern.
Il comando completo quindi sarà:
grep "2024:08:" /var/log/nginx/access.log \
| grep -v "UptimeRobot" \
| awk -F '[ "\[\]]+' \
'{print "\t" $4 "\t" $6 "\t" $10 "\t" $7}' \
| grep -vE "\.(jpg|js|css)(\?.*)?$"
In conclusione
Grazie alle informazioni che ti ho fornito, potrai analizzare qualsiasi file di log testuale ed estrapolare le informazioni di cui hai bisogno per svolgere un’analisi accurata delle informazioni.
Ricorda però che non esiste una formula assoluta per estrapolare le informazioni di cui hai bisogno che risulterà sempre valida. In alcuni casi potresti aver bisogno di verificare gli user agent in altri casi i metodi.
Infine ricorda che alcuni CMS producono gli asset apparentemente statici (jpg, css e js) partendo da computazioni lato server che prevedono esecuzione di codice sul server. Quindi, ai fini di un’analisi di sicurezza, non dare per scontato che un file jpg sia sempre un’immagine.
Questo articolo nasce da un’attività costante che in Axio Studio svolgiamo sui server dei nostri clienti per prevenire alcuni tipi di attacchi informatici. Se vuoi condividere le tue metodologie di analisi che adotti in azienda lascia un commento.
Foto di Gabriel Heinzer su Unsplash