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 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; \item Wireshark\cite{WIRESHARK:1} per monitorare la rete. \end{itemize} \section{API} Prendendo ad esempio l'API REST di Github \cite{GITHUB:1}, si vede come, anche nella realtà, si usa un endpoint generale per visualizzare le informazioni di un determinato utente. \textbf{Cosa proveremo a fare noi?} Diamo per assodato che un utente è autorizzato a visualizzare quell'endpoint (e così anche i corrispettivi di PUT e DELETE) solo se è l'owner di quella risorsa o se ha permessi speciali: da staffer nel nostro caso. \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: controlla se l'utente dell'url è lo stesso del token di autorizzazione o altrimenti che l'utente dell'autorizazzione (il quale token è creato dal server) sia uno staffer. \begin{lstlisting} async fn get_user(Path(user_id): Path, claims: Claims) -> Result, AppError> { let claimed = match User::find_by_id(claims.user_id).await { Ok(user) => user, Err(_) => { return Err(AppError::NotFound("User not found".to_string())); } }; if user_id != claimed.id { if !(claimed.is_staff.unwrap()) { return Err(AppError::Unauthorized); } } match User::find_by_id(user_id).await { Ok(user) => Ok(Json(user)), Err(_) => Err(AppError::NotFound("User not found".to_string())), } } \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} let token_data = decode::(bearer.token(), &KEYS.decoding, &Validation::default()) .map_err(|_| AppError::InvalidToken)?; \end{lstlisting} Il problema sta nell'inizializzazione della codifica/decodifica di JWT, in particolare quando definiamo il secret, dato che non fa un controllo della sicurezza di tale stringa. \begin{lstlisting} static KEYS: Lazy = 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. \section{App mobile} Il codice dell'app è presente a \underline{\url{https://git.dcariotti.me/m6-ie/tree/app}}. È un'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; \item Me: visualizza le informazioni dell'utente loggato. \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 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 $ vi .env # Chi fa la build conosce effettivamente l'URL dell'API $ npm run build --production $ npx cap copy android $ npx cap sync android $ cd android $ export ANDROID_SDK_ROOT="" $ ./gradlew assembleDebug \end{lstlisting} L'ultimo comando creerà un APK valido dentro \emph{./build/outputs/apk/debug/app-debug.apk}. Da non confondere quindi con la creazione di pacchetti AAB\cite{APKVSAAB:1} per le release. \subsection{Configurazione Android Studio} Scaricato e installato il pacchetto dal link ufficiale bisognerà inoltre installare anche l'SDK e un Device, ovvero un emulatore di un dispositivo Android. \begin{figure}[h] \centering \includegraphics[width=0.2\textwidth]{data/android-studio-screenshot} \caption{Screenshot di Android Studio in Ubuntu focal} \end{figure} Nell'esempio qui di seguito io userò SDK 30 su un Pixel 4.\\ \\ Procediamo quindi al profiling dell'applicazione come da \textbf{Figure 3.1}. Avviando l'emulatore attraverso \emph{Shift+F10} vedremo l'applicazione dentro il device Android, Google Pixel in questo caso. \begin{figure}[h] \centering \includegraphics[width=0.2\textwidth]{data/pixel-device} \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; ecco che viene in aiuto Wireshark.