Android Package Kit (APK)
I file apk sono dei packages utilizzati per distribuire le applicazioni Android sotto forma di archivio. Infatti cambiando l’estensione in .zip è possibile vedere i file al suo interno. I file APK hanno la seguente struttura:
- assets: cartella contenente gli assets dell’app.
- res: cartella contenente le risorse che non sono compilate all’interno di resources.arsc. Sono tutte risorse fatta eccezione per i file che ritrovano in res/values. tutti i file XML sono convertiti in binary XML e i file .png sono ottimizzati per ottimizzare le performance a runtime.
- lib: contiene le librerie native utilizzate dall’app. Può contenere varie cartelle a seconda dell’architettura della CPU.
- META-INF: Meta dati del file apk. Contiene MANIFEST.MF, CERT.RSA e CERT.SF.
- MANIFEST.MF: Info essenziali sull’app come nome, permessi richiesti, requisiti software etc.
- CERT.RSA: Certificato pubblico dell’app.
- CERT.SF: Lista di tutti i file contenuti nell’app con relativo
SHA1digest. - AndroidManifest.xml: Contiene i metadati e alcune configurazioni dell’app.
- classes.dex: Codice app in formato dex (Dalvik EXecutable format).
- resources.arsc: risorse precompilate come stringhe, stili etc.
AndroidManifest.xml
Il file manifest descrive la struttura dell’app, i suoi componenti (activities, services, content providers, intents) e i permessi richiesti. Contiene inoltre metadati generali dell’app, come l’icona dell’app, il numero di versione e il tema. Il file può elencare altre informazioni, come le API compatibili (versione SDK minima, mirata e massima) e il tipo di archiviazione su cui può essere installato (esterno o interno). Inoltre specifica se è possibile debuggare o effettuare il backup dei dati dell’app.
Esempio di AndroidManifest.xml:
<manifest
package="com.owasp.myapplication"
android:versionCode="0.1" >
<uses-sdk android:minSdkVersion="12"
android:targetSdkVersion="22"
android:maxSdkVersion="25" />
<uses-permission android:name="android.permission.INTERNET" />
<provider
android:name="com.owasp.myapplication.MyProvider"
android:exported="false" />
<receiver android:name=".MyReceiver" >
<intent-filter>
<action android:name="com.owasp.myapplication.myaction" />
</intent-filter>
</receiver>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Material.Light" >
<activity
android:name="com.owasp.myapplication.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>
Componenti App
Di seguito un elenco di componenti principali che costituiscono l’app e le sue funzionalità:
- Activities
- Fragments
- Inter-Process Communication
- Intents
- Broadcast receivers
- Content providers and services
Activities
Le activities sono le schermate dell’applicazione. Se ci sono 3 differenti schermate ci sono 3 differenti activities. Contengono tutti gli elementi dell’interfaccia: fragments, viste e layouts.
Ogni activity deve essere dichiarata nel manifest di android:
<activity android:name="ActivityName"></activity>
Le activities hanno un loro ciclo di vita e possono trovarsi negli stati: active, paused, stopped, inactive. Di conseguenza le activities possono implementare i seguenti eventi:
- onCreate
- onSaveInstanceState
- onStart
- onResume
- onRestoreInstanceState
- onPause
- onStop
- onRestart
- onDestroy
Le Activities possono avere un attributo exported (nel manifest) che se impostato a true consente ad altre app di avviarle. Supponendo di avere una schermata post login esportata potrebbe essere possibile usufruire delle funzionalità dell’app bypassando il login.
Le activities devono avere l’attributo android: exported=”false”, nel caso debba essere true è necessario definire i permessi per l’accesso all’activity.
Per esempio se l’app A vuole utilizzare la funzionalità di condivisione post dell’app B “SocialApp”, l’app B deve dichiarare il permesso che un’app esterna che sta richiamando l’activity deve avere.
<!--// app B //-->
<manifest>
<activity android:name="...."
android:permission=”com.google.socialapp.permission.SHARE_POST”
/>
Quindi l’app A, per poter richiamare l’app B SocialApp, deve avere un permesso che fa scopa con quello impostato nel manifest dell’app B.
<!--// app A //-->
<manifest>
<uses-permission android:name="com.google.socialapp.permission.SHARE_POST" />
</manifest>
Riferimenti: https://developer.android.com/guide/components/activities/intro-activities
Il fatto che un’activity possa essere avviata da un’app esterna non costituisce di per sé una vulnerabilità. È però da approfondire, attraverso l’analisi del codice, se l’activity richiamata risponde con dei dati sensibili. Occorre quindi verificare se è stato utilizzato il metodo setResult all’interno del codice dell’activity e bisogna cercare di capire se i dati passati a setResult sono dati sensibili.
Fragments
Rappresenta un comportamento o una porzione di user interface all’interno dell’activity.
Inter-Process Communication
Dal momento che ciascun’ applicazione gira nella propria sandbox, IPC permette alle app di comunicare tra di loro quando necessario. IPC di Android si basa su Binder, un’implementazione custom di OpenBinder.
Riferimenti: https://medium.com/@pkurumbudel/android-system-ipc-mechanisms-3d3b46affa3c
https://en.wikipedia.org/wiki/OpenBinder
Intents
Intent messaging è un framework di comunicazione asincrona basato su Binder. Questo framework permette comunicazione punto-punto e publish-subscribe. Un intent è un messaging object che può essere usato per richiedere un’azione da un componente di un’altra app.
Gli intenti hanno 3 casi d’uso fondamentali:
- Avvio di un’activity
- Avvio di un service
- Consegna di un broadcast
Intents espliciti: specificano quale applicazione soddisferà l’intento, fornendo il nome del pacchetto dell’app di destinazione o un nome di classe del componente completo. Usato solitamente per avviare un componente nella propria app, poiché si conosce il nome della classe dell’attività o del servizio che si desidera avviare. Ad esempio, è possibile avviare una nuova attività all’interno dell’app in risposta a un’azione dell’utente o avviare un servizio per scaricare un file in background.
Intent intent = new Intent(this, myActivity.myClass);
Intents impliciti: non viene nominato un componente specifico, ma si dichiara un’azione generale da eseguire che consente a un componente di un’altra app di gestirlo. Ad esempio, se si desidera mostrare all’utente la posizione su una mappa, è possibile utilizzare un intent implicito per richiedere che un’altra app mostri la posizione specifica su una mappa.
Intent email = new Intent(Intent.ACTION_SEND, Uri.parse("mailto:"));
Un intent filter è un’indicazione nel manifest di Android che specifica il tipo di intent che l’app si aspetta di ricevere, viene utilizzato per gli intents impliciti. Il sistema Android cerca in tutte le app un intent filter con un’action che corrisponda a quella dell’intento e quando viene trovata una corrispondenza, il sistema avvia l’attività corrispondente invocando il suo onCreate(). Se viene trovata più di un’opzione, viene mostrata la scelta all’utente (Con quale app vuoi aprire?)
Android utilizza gli intents per trasmettere messaggi alle app (chiamata o sms in entrata), messaggi relativi all’alimentazione (batteria scarica), e cambiamenti sulla rete (perdita di connessione). Dati extra possono essere aggiunti agli intenti.
Dal punto di vista della sicurezza, dal momento che il destinatario di un intent implicito non è specificato dall’applicazione, è importante notare che i dati inviati tramite l’intent possono essere intercettati da un’app malevola. Gli implicit intents non devono inviare dai sensibili.
Broadcast receivers
Ricevono i broadcast intents, un tipo di intenti che possono essere ricevuti da molteplici app. E’ importante che durante l’invio di broadcast non siano inviati dati sensibili. Per fare in modo che solo una determinata app riceva il broadcast occorre utilizzare Intent.setPackage
. In alternativa, è possibile specificare un permesso nel momento in cui il broadcast viene inviato. L’app che riceve il broadcast specificherà uses-permission nel manifest di Android che avrà come valore il nome del permesso impostato dall’app di partenza.
Permettono alle app di ricevere le notifiche dalle altre app o dal sistema stesso. Solitamente sono utilizzati per aggiornare le interfacce, far partire servizi, aggiornamento di contenuti e creazione di notifiche.
Ci sono due modi per far conoscere al sistema la presenza del broadcast receiver, uno è dichiarandolo all’interno del manifest di Android. Deve essere specificata un’associazione tra il broadcast receiver e l’intent filter per indicare l’azione che il broadcast receiver deve compiere.
<receiver android:name=".MyReceiver" >
<intent-filter>
<action android:name="com.owasp.myapplication.MY_ACTION" />
</intent-filter>
</receiver>
Nel momento in cui, per qualsiasi componente, viene specificato un intent filter il componente viene esportato automaticamente anche se non include l’attributo android:exported
che viene automaticamente impostato a true. Senza intent filter è impostato a false.
Per assicurarsi che il broadcast non vada all’esterno dell’app si consiglia di utilizzare il metodo sendBroadcast
della classe LocalBroadCastManager
anziché della classe Context
. Con questo metodo non è necessario esportare un receiver e le informazioni inviate tramite broadcast non saranno esposte.
L’altro metodo per registrare un broadcast receiver è dinamicamente all’interno del codice col metodo Context.registerReceiver
.
Durante l’analisi è importante esaminare gli intents passati al metodo onReceive
, bisogna assicurarsi che i dati siano validati e che funzionalità critiche non possano essere avviate da un’app malevola. Occorre verificare che solo l’app legittima possa ricevere il broadcast attraverso l’uso dei permessi e che qualsiasi tentativo di leggere i risultati attraverso il metodo getResultX validi i dati ricevuti.
Sticky Broadcast
Sono Broadcast accessibili sempre, anche dopo tanto tempo che sono stati inviati. Deprecati da API 21. non usare Sticky broadcasts.
Deep Links
I deep links permettono di trigger are un intent attraverso un url. Per ricevere un intent da un deep link occorre impostare una category android.intent.category.BROWSABLE
nell’intent-filter all’interno del manifest dell’app.
<activity
android:name="com.example.android.GizmosActivity"
android:label="@string/title_gizmos" >
<intent-filter android:label="@string/filter_view_http_gizmos">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "http://www.example.com/gizmos” -->
<data android:scheme="http"
android:host="www.example.com"
android:pathPrefix="/gizmos" />
<!-- note that the leading "/" is required for pathPrefix-->
</intent-filter>
<intent-filter android:label="@string/filter_view_example_gizmos">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "example://gizmos” -->
<data android:scheme="example"
android:host="gizmos" />
</intent-filter>
</activity>
Content providers
Android utilizza SQLite per archiviare i dati in modo permanente. Per impostazione predefinita, un database appartenente a una determinata app è accessibile solo all’ app stessa. Tuttavia, i content providers offrono un ottimo meccanismo per astrarre le origini dei dati (inclusi database SQLite e file di testo) e forniscono inoltre un sistema standard ed efficiente per condividere i dati tra le app, comprese le app native. Per essere accessibile da altre app, un content provider deve essere dichiarato esplicitamente nel file manifest Android. Se i content providers non vengono dichiarati, non verranno esportati e potranno essere chiamati solo dall’app che li crea.
Sono implementati attraverso uno schema di accesso URI e usano tutti il modello content://. Indipendentemente dal tipo di origine dati, lo schema di accesso è sempre lo stesso. I content providers offrono tutte le normali operazioni del database: creazione, lettura, aggiornamento, eliminazione. Ciò significa che qualsiasi app con diritti adeguati nel file manifest può manipolare i dati di altre app.
Per limitare l’utilizzo dei providers possono essere impostati dei permessi con il protection level appropriato all’interno del manifest dell’app.
I content providers, oltre all’attributo permission, possono avere readPermission and writePermission per stabilire il tipo di accesso che l’app può avere nei confronti del provider.
android:grantUriPermissions
serve per impostare un’eccezione temporanea relativamente agli altri permessi impostati nel content provider.
Se l’attributo android:grantUriPermissions di un content provider è “true”, l’autorizzazione può essere concessa per tutti i dati di competenza del provider. Se invece l’ attributo è “false”, l’autorizzazione può essere concessa solo ai sottoinsiemi di dati specificati da <grant-uri-permission>
.
Supponendo di avere un’ app che gestisce le email, una mail con una immagine allegata e un’app di visualizzazione immagini, per poter permettere al visualizzatore di aprire l’immagine del messaggio attraverso un url, occorrerà impostare l’elemento grant-uri-permission all’interno del manifest dell’app.
<grant-uri-permission>
ha 3 attributi:
- path: specifica il percorso che i permessi impostati non devono considerare.
- pathPrefix: Serve per escludere dai permessi i child paths.
- pathPattern: permette l’utilizzo di wildcards per matchare molteplici endpoints.
Services
I services sono componenti di Android che eseguono attività in background (elaborazione dei dati, intents di avvio e notifiche, ecc.). I services, come le attività, vengono eseguiti nel thread dell’app principale. Un service non crea il proprio thread e non viene eseguito in un processo separato se non diversamente specificato.
Per analizzare l’utilizzo dei services occorre trovare il metodo onBind all’interno del codice. onBind è il momento in cui l’app inizia ad interagire col servizio. Occorrerà seguire la logica dell’app per cercare di capire quali azioni sono invocate e quali dati sono passati al servizio.
Un altro metodo da controllare è onStartCommand
e analizzare l’intent che viene passato al metodo. Occorre analizzare se ci sono funzionalità sensibili invocate dall’intento o se ci sono Extra che non sono sati sanitizzati.
Permessi
Poiché le app sono installate in una sandbox isolata, Android fornisce un sistema con un set predefinito di autorizzazioni per determinate funzionalità a cui l’app può avere accesso Ad esempio per permettere all’applicazione di utilizzare la fotocamera, deve essere richiesta l’autorizzazione android.permission.CAMERA. Prima di Android 6.0 (livello API 23), tutte le autorizzazioni richieste da un’app erano concesse al momento dell’installazione. Dal livello API 23 in poi, l’utente deve approvare alcune richieste di autorizzazione durante l’esecuzione dell’app.
Le app possono richiedere permessi, in questo caso di leggere SMS (triggerano le conferme richieste al momento dell’installazione dell’app o durante ‘utilizzo di una funzionalità):
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.permissions.sample" ...>
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<application>...</application>
</manifest>
Da android 6.0 (API 23) in avanti, in caso di dangerous permission, lo sviluppatore deve verificare a runtime che l’utente abbia concesso il permesso richiesto dall’app.
O dichiarare permessi per esporre funzionalità ad altre app:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.permissions.sample" ...>
<permission
android:name="com.permissions.sample.ACCESS_USER_INFO"
android:protectionLevel="signature" />
<application>...</application>
</manifest>
L’esempio precedente definisce un nuovo permesso denominato com.permissions.sample.ACCESS_USER_INFO con il livello di protezione signature. Tutti i componenti protetti con questa autorizzazione sono accessibili solo dalle app firmate con lo stesso certificato di sviluppatore.
I permessi di Android possono essere quelli predefiniti o possono essere creati dei permessi propri. Le autorizzazioni sono classificate secondo 4 diversi livelli di protezione:
- Normal: livello più basso di protezione, l’app può accedere alle features della sandbox col minimo rischio per le altre app, per l’utente o per il sistema. Non richiede l’approvazione dell’utente.
- Dangerous: Il permesso consente all’app di eseguire azioni che influenzano la privacy dell’utente o le normali operazioni di utilizzo del dispositivo. L’utente può scegliere se attivare questo tipo di autorizzazione.
- Signature: Questo permesso è concesso solo se l’app che lo richiede è stata firmata con lo stesso certificato dell’app che ha dichiarato il permesso. Se la firma corrisponde, l’autorizzazione viene concessa automaticamente. (es. due app dello stesso sviluppatore).
- SystemOrSignature: Questa autorizzazione è concessa solo alle app di sistema o firmate con lo stesso certificato con cui l’app che ha dichiarato l’autorizzazione è stata firmata.
Durante le analisi i permessi pericolosi vengono indicati come informational per mettere a conoscenza lo sviluppatore che deve utilizzare esclusivamente le autorizzazioni realmente richieste dalle funzionalità dell’applicazione.
Riferimenti: https://developer.android.com/reference/android/Manifest.permission.html
I permessi possono essere estesi anche alle activities, ai services, ai content providers e ai broadcast receivers.
WebViews
Le WebViews sono dei browsers implementati delle app. Possono renderizzare HTML ed eseguire JavaScript. Le pagine possono essere caricate da remoto o possono essere caricate dal device locale. Può capitare che esistano dei file html, dei template salvati in locale al momento dell’installazione e il contenuto modificato dinamicamente a runtime prelevando i contenuti dal backend.
Esistono 2 tipi di WebViews:
- WebViewClient: simple HTML rendering. alert() non funziona. Hanno il loro cookie jar, non hanno accesso agli altri cookies dell’app e nemmeno ali cookie del browser. Occorre controllare che i dati passati a loadUrl, loadData, loadDataWithBaseURL non provengano da input controllati dall’utente e che eventualmente siano sanitizzati.
- WebChrome: chrome browser
Possibilmente disabilitare javaScript nelle webviews. setJavaScriptEnabled(false)
Metodi da controllare: loadUrl
, LoadData
, loadDataWithBaseURL
Verificare che i dati passati ai metodi siano sanitizzati se derivano da input controllati dall’utente.
Un’altra criticità relativa alle WebViews è rappresentata dal metodo addJavascriptInterface
che se impostata a true permette a Javascript di accedere ai metodi pubblici Java dell’app. (Android 4.1)
Nelle versioni più recenti di Android è possibile interagire solo coi metodi che hanno il decorator @JavascriptInterface
.
WebView myWebView = (WebView) findViewById(C0000R.id.webView1);
myWebView.setWebChromeClient(new WebChromeClient());
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.addJavascriptInterface(new OMTG_ENV_005_JS_Interface(this), "Android");
myWebView.loadUrl("https://rawgit.com/sushi2k/AndroidWebView/master/webview.htm");
public class OMTG_ENV_005_JS_Interface {
Context mContext;
OMTG_ENV_005_JS_Interface(Context c) {
this.mContext = c;
}
public OMTG_ENV_005_JS_Interface() {
}
@JavascriptInterface
public String returnString() {
return "Secret String";
}
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(this.mContext, toast, 0).show();
}
}
//PoC pagina HTML
function fireToastMessage() {
window.Android.showToast("this is executed by JavaScript");
}