diff options
-rw-r--r-- | docs/chapters/jwt-attacks.tex | 8 | ||||
-rw-r--r-- | docs/conf.tex | 2 | ||||
-rw-r--r-- | docs/m6.tex | 75 | ||||
-rw-r--r-- | docs/refs.bib | 9 |
4 files changed, 86 insertions, 8 deletions
diff --git a/docs/chapters/jwt-attacks.tex b/docs/chapters/jwt-attacks.tex index 5a67c4f..cb32afb 100644 --- a/docs/chapters/jwt-attacks.tex +++ b/docs/chapters/jwt-attacks.tex @@ -21,7 +21,9 @@ Questo token quindi è passato come header HTTP alla chiave d'autorizzazione. POST /v1/users/2/ HTTP/2 Host: example.com Referer: https://example.com -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxfQ.jYyRJbb0WImFoUUdcslQQfwnXTHJzne-6tsPd8Hrw0I +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. + eyJ1c2VyX2lkIjoxfQ. + jYyRJbb0WImFoUUdcslQQfwnXTHJzne-6tsPd8Hrw0I [...] \end{lstlisting} @@ -51,7 +53,9 @@ HTTP/2 200 OK \end{lstlisting} Questo potrebbe essere un bell'attacco, peccato però che tutti (spero) i backend che adoperano l'uso di JWT (spesso anche per autenticazione) usano un payload del tipo: \begin{lstlisting} -$ echo "eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjY0NjE1NDcxLCJqdGkiOiIyMGM3Nzk2YTljM2Y0Yjk4YmM3ODdkNDRmNzRiNGE0YyIsInVzZXJfaWQiOjF9" | base64 -d +$ echo "eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjY0NjE1NDc +xLCJqdGkiOiIyMGM3Nzk2YTljM2Y0Yjk4YmM3ODdkNDRmNzRiNGE0YyIsInV +zZXJfaWQiOjF9" | base64 -d {"token_type":"access","exp":1664615471,"jti":"20c7796a9c3f4b98bc787d44f74b4a4c","user_id":1} \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}. diff --git a/docs/conf.tex b/docs/conf.tex index c2c8521..07abcd9 100644 --- a/docs/conf.tex +++ b/docs/conf.tex @@ -9,7 +9,7 @@ \definecolor{mauve}{rgb}{0.58,0,0.82} \lstset{frame=tb, - language=Java, + language=C, aboveskip=3mm, belowskip=3mm, showstringspaces=false, diff --git a/docs/m6.tex b/docs/m6.tex index a7ac7c7..e1f1cb8 100644 --- a/docs/m6.tex +++ b/docs/m6.tex @@ -7,7 +7,7 @@ \small\textsc{Dipartimento di Matematica e Informatica\\Corso di Internet Security}\\ \Huge\textbf{M6: Insecure Authorization\\Attack side}\\ \author{Santo Cariotti} - \date{x Settembre 2022} + \date{x y z} } \usepackage{graphicx} @@ -25,18 +25,83 @@ \input{chapters/jwt-attacks} \chapter{Attacco ad una API} -Escluso l'attacco a JWT passiamo ad un possibile attacco rivolto ad un API. - 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. 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: \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; - \item Un'applicazione mobile che fa richieste a tale API. + \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; + \item Wireshark\cite{WIRESHARK:1} per monitorare la rete. \end{itemize} +\section{API} +Nella realtà, come questa API pubblica \cite{REDDIT:1} fa, 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 +Il codice di questo servizio è presente al link \underline{\url{https://git.dcariotti.me/m6-ie/tree/server}}. +\newline\newline +La parte incriminata è la route qui sotto. Qui non si limita a ritornare la riga utente che corrisponde all'ID utente passato dall'header. + +\begin{lstlisting} +async fn get_user(claims: Claims) -> Result<Json<UserList>, AppError> { + match User::find_by_id(claims.user_id).await { + Ok(user) => Ok(Json(user)), + Err(_) => Err(AppError::NotFound), + } +} +\end{lstlisting} + +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::<Claims>(bearer.token(), &KEYS.decoding, &Validation::default()) + .map_err(|_| AppError::InvalidToken)?; +\end{lstlisting} + +infatti il problema sta nell'inizializzazione della codifica/decodifica di JWT, in particolare quando definiamo il secret. + +\begin{lstlisting} +static KEYS: Lazy<Keys> = Lazy::new(|| { + let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"); + Keys::new(secret.as_bytes()) +}); + +impl Keys { + fn new(secret: &[u8]) -> Self { + Self { + encoding: EncodingKey::from_secret(secret), + decoding: DecodingKey::from_secret(secret), + } + } +} +\end{lstlisting} + +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. + +\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 \cite{JWT_SECRETS_LIST:1} 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.eyJ1c2VyX2lkIjoxMywiZXhwIjoxNjY2Mjk0Nzk2fQ.ay_RPoeTuV4e +lBFqqCdTzF64GPcoEDOlJN2DUAOqwds:hello + +Session..........: hashcat +Status...........: Cracked +Hash.Type........: JWT (JSON Web Token) +Hash.Target......: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIj...AOqwds +\end{lstlisting} +Qui vediamo come sia riuscito a trovare il secret, ovvero la stringa \emph{hello}. \bibliography{refs} \bibliographystyle{ieeetr} diff --git a/docs/refs.bib b/docs/refs.bib index 59f7b4c..60ad0ff 100644 --- a/docs/refs.bib +++ b/docs/refs.bib @@ -50,3 +50,12 @@ @MISC{WIRESHARK:1, HOWPUBLISHED="\url{https://www.wireshark.org/}" } +@MISC{REDDIT:1, + HOWPUBLISHED="\url{https://www.reddit.com/dev/api#GET_api_v1_me}" +} +@MISC{JWT_SECRET_LIST:1, + HOWPUBLISHED="\url{https://raw.githubusercontent.com/wallarm/jwt-secrets/master/jwt.secrets.list}" +} +@MISC{HASHCAT, + HOWPUBLISHED="\url{https://hashcat.net/hashcat/}" +} |