Categoria OWASP: MASVS-CODE: Code Quality
Panoramica
Non è raro vedere applicazioni che implementano funzionalità che consentono agli utenti di trasferire dati o interagire con altri dispositivi utilizzando comunicazioni a radiofrequenza (RF) o connessioni via cavo. Le tecnologie più comuni utilizzate in Android a questo scopo sono Bluetooth classico (Bluetooth BR/EDR), Bluetooth Low Energy (BLE), Wi-Fi P2P, NFC e USB.
Queste tecnologie vengono in genere implementate in applicazioni che dovrebbero comunicare con accessori per la smart home, dispositivi di monitoraggio della salute, chioschi dei trasporti pubblici, terminali di pagamento e altri dispositivi con Android.
Come qualsiasi altro canale, le comunicazioni da macchina a macchina sono soggette ad attacchi che mirano a compromettere il limite di attendibilità stabilito tra due o più dispositivi. Gli utenti malintenzionati possono sfruttare tecniche come lo spoofing dei dispositivi per eseguire un'ampia gamma di attacchi al canale di comunicazione.
Queste API devono essere utilizzate con attenzione, poiché gli errori durante l'implementazione dei protocolli di comunicazione possono comportare l'esposizione dei dati dell'utente o dei dati sul dispositivo a terze parti non autorizzate. Nel peggiore dei casi, gli aggressori potrebbero essere in grado di assumere il controllo da remoto di uno o più dispositivi, ottenendo di conseguenza l'accesso completo ai contenuti del dispositivo.
Impatto
L'impatto può variare a seconda della tecnologia da dispositivo a dispositivo implementata nell'applicazione.
L'utilizzo o la configurazione errati dei canali di comunicazione da macchina a macchina possono lasciare il dispositivo dell'utente esposto a tentativi di comunicazione non attendibili. Ciò può rendere il dispositivo vulnerabile ad attacchi aggiuntivi come man-in-the-middle (MiTM), command injection, DoS o spoofing.
Rischio: intercettazione di dati sensibili su canali wireless
Quando si implementano meccanismi di comunicazione da macchina a macchina, è necessario prestare attenzione sia alla tecnologia utilizzata sia al tipo di dati da trasmettere. Sebbene le connessioni via cavo siano in pratica più sicure per queste attività, in quanto richiedono un collegamento fisico tra i dispositivi coinvolti, i protocolli di comunicazione che utilizzano radiofrequenze come Bluetooth classico, BLE, NFC e Wi-Fi P2P possono essere intercettati. Un aggressore potrebbe essere in grado di impersonare uno dei terminali o dei punti di accesso coinvolti nello scambio di dati, intercettando la comunicazione via etere e ottenendo di conseguenza l'accesso ai dati sensibili dell'utente. Inoltre, le applicazioni dannose installate sul dispositivo, se vengono concesse le autorizzazioni di runtime specifiche per la comunicazione , potrebbero essere in grado di recuperare i dati scambiati tra i dispositivi leggendo i buffer dei messaggi di sistema.
Mitigazioni
Se l'applicazione richiede lo scambio di dati sensibili da macchina a macchina su canali wireless, è necessario implementare nel codice dell'applicazione soluzioni di sicurezza a livello di applicazione, come la crittografia. In questo modo, gli aggressori non potranno intercettare il canale di comunicazione e recuperare i dati scambiati in testo non crittografato. Per ulteriori risorse, consulta la documentazione sulla crittografia.
Rischio: injection di dati dannosi wireless
I canali di comunicazione da macchina a macchina wireless (Bluetooth classico, BLE, NFC, Wi-Fi P2P) possono essere manomessi utilizzando dati dannosi. Gli aggressori sufficientemente esperti possono identificare il protocollo di comunicazione in uso e manomettere il flusso di scambio di dati, ad esempio impersonando uno degli endpoint e inviando payload appositamente creati. Questo tipo di traffico dannoso può compromettere la funzionalità dell'applicazione e, nel peggiore dei casi, causare un comportamento imprevisto dell'applicazione e del dispositivo o provocare attacchi come DoS, command injection o acquisizione del controllo del dispositivo.
Mitigazioni
Android fornisce agli sviluppatori API potenti per gestire le comunicazioni da macchina a macchina come Bluetooth classico, BLE, NFC e Wi-Fi P2P. Queste API devono essere combinate con una logica di convalida dei dati implementata con attenzione per sanificare tutti i dati scambiati tra due dispositivi.
Questa soluzione deve essere implementata a livello di applicazione e deve includere controlli che verifichino se i dati hanno la lunghezza e il formato previsti e se contengono un payload valido che può essere interpretato dall'applicazione.
Il seguente snippet mostra un esempio di logica di convalida dei dati. È stato implementato sull'esempio degli sviluppatori Android per l'implementazione del trasferimento di dati Bluetooth:
Kotlin
class MyThread(private val mmInStream: InputStream, private val handler: Handler) : Thread() {
private val mmBuffer = ByteArray(1024)
override fun run() {
while (true) {
try {
val numBytes = mmInStream.read(mmBuffer)
if (numBytes > 0) {
val data = mmBuffer.copyOf(numBytes)
if (isValidBinaryData(data)) {
val readMsg = handler.obtainMessage(
MessageConstants.MESSAGE_READ, numBytes, -1, data
)
readMsg.sendToTarget()
} else {
Log.w(TAG, "Invalid data received: $data")
}
}
} catch (e: IOException) {
Log.d(TAG, "Input stream was disconnected", e)
break
}
}
}
private fun isValidBinaryData(data: ByteArray): Boolean {
if (// Implement data validation rules here) {
return false
} else {
// Data is in the expected format
return true
}
}
}
Java
public void run() {
mmBuffer = new byte[1024];
int numBytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs.
while (true) {
try {
// Read from the InputStream.
numBytes = mmInStream.read(mmBuffer);
if (numBytes > 0) {
// Handle raw data directly
byte[] data = Arrays.copyOf(mmBuffer, numBytes);
// Validate the data before sending it to the UI activity
if (isValidBinaryData(data)) {
// Data is valid, send it to the UI activity
Message readMsg = handler.obtainMessage(
MessageConstants.MESSAGE_READ, numBytes, -1,
data);
readMsg.sendToTarget();
} else {
// Data is invalid
Log.w(TAG, "Invalid data received: " + data);
}
}
} catch (IOException e) {
Log.d(TAG, "Input stream was disconnected", e);
break;
}
}
}
private boolean isValidBinaryData(byte[] data) {
if (// Implement data validation rules here) {
return false;
} else {
// Data is in the expected format
return true;
}
}
Rischio: injection di dati dannosi USB
Le connessioni USB tra due dispositivi possono essere prese di mira da un utente malintenzionato interessato a intercettare le comunicazioni. In questo caso, il collegamento fisico richiesto costituisce un ulteriore livello di sicurezza, in quanto l'attaccante deve ottenere l'accesso al cavo che collega i terminali per poter intercettare qualsiasi messaggio. Un altro vettore d'attacco è rappresentato da dispositivi USB non attendibili che, intenzionalmente o meno, vengono collegati al dispositivo.
Se l'applicazione filtra i dispositivi USB utilizzando PID/VID per attivare funzionalità specifiche in-app, gli aggressori potrebbero essere in grado di manomettere i dati inviati tramite il canale USB impersonando il dispositivo legittimo. Gli attacchi di questo tipo possono consentire agli utenti malintenzionati di inviare sequenze di tasti al dispositivo o di eseguire attività dell'applicazione che, nel peggiore dei casi, possono portare all'esecuzione di codice da remoto o al download di software indesiderato.
Mitigazioni
È necessario implementare una logica di convalida a livello di applicazione. Questa logica deve filtrare i dati inviati tramite USB verificando che la lunghezza, il formato e il contenuto corrispondano al caso d'uso dell'applicazione. Ad esempio, un monitor della frequenza cardiaca non deve essere in grado di inviare comandi di sequenze di tasti.
Inoltre, quando possibile, è necessario valutare la possibilità di limitare il numero di pacchetti USB che l'applicazione può ricevere dal dispositivo USB. In questo modo, i dispositivi dannosi non possono eseguire attacchi come il rubber ducky.
Questa convalida può essere eseguita creando un nuovo thread per controllare il
contenuto del buffer, ad esempio in caso di bulkTransfer:
Kotlin
fun performBulkTransfer() {
// Stores data received from a device to the host in a buffer
val bytesTransferred = connection.bulkTransfer(endpointIn, buffer, buffer.size, 5000)
if (bytesTransferred > 0) {
if (//Checks against buffer content) {
processValidData(buffer)
} else {
handleInvalidData()
}
} else {
handleTransferError()
}
}
Java
public void performBulkTransfer() {
//Stores data received from a device to the host in a buffer
int bytesTransferred = connection.bulkTransfer(endpointIn, buffer, buffer.length, 5000);
if (bytesTransferred > 0) {
if (//Checks against buffer content) {
processValidData(buffer);
} else {
handleInvalidData();
}
} else {
handleTransferError();
}
}
Rischi specifici
Questa sezione raccoglie i rischi che richiedono strategie di mitigazione non standard o che sono stati mitigati a un determinato livello di SDK e sono qui per completezza.
Rischio: Bluetooth – tempo di rilevabilità errato
Come evidenziato nella documentazione per gli sviluppatori Android su Bluetooth, durante la
configurazione dell'interfaccia Bluetooth all'interno dell'applicazione, l'utilizzo del
startActivityForResult(Intent, int) metodo per attivare la
rilevabilità del dispositivo e l'impostazione di EXTRA_DISCOVERABLE_DURATION su zero farà in modo che il dispositivo sia rilevabile finché l'applicazione è in esecuzione in background o in primo piano. Per quanto riguarda la specifica Bluetooth classica, i dispositivi rilevabili trasmettono costantemente messaggi di rilevamento specifici che consentono ad altri dispositivi di recuperare i dati sul dispositivo o di connettersi a esso. In questo scenario, una terza parte dannosa può intercettare questi messaggi e connettersi al dispositivo con Android. Una volta connesso, un aggressore può eseguire ulteriori attacchi come il furto di dati, DoS o command injection.
Mitigazioni
EXTRA_DISCOVERABLE_DURATION non deve mai essere impostato su zero. Se il parametro EXTRA_DISCOVERABLE_DURATION non è impostato, per impostazione predefinita Android rende i dispositivi rilevabili per 2 minuti. Il valore massimo che può essere impostato per il parametro EXTRA_DISCOVERABLE_DURATION è di 2 ore (7200 secondi). È consigliabile mantenere la durata di rilevabilità il più breve possibile, in base al caso d'uso dell'applicazione.
Rischio: NFC – intent-filter clonati
Un'applicazione dannosa può registrare intent-filter per leggere tag NFC o dispositivi con NFC specifici. Questi filtri possono replicare quelli definiti da un'applicazione legittima, consentendo a un aggressore di leggere il contenuto dei dati NFC scambiati. Tieni presente che, quando due attività specificano gli stessi intent-filter per un tag NFC specifico, viene visualizzato il selettore di attività, pertanto l'utente dovrà comunque scegliere l'applicazione dannosa per il successo dell'attacco. Tuttavia, combinando gli intent-filter con il cloaking, questo scenario è comunque possibile. Questo attacco è significativo solo nei casi in cui i dati scambiati tramite NFC possono essere considerati altamente sensibili.
Mitigazioni
Quando si implementano funzionalità di lettura NFC all'interno di un'applicazione, è possibile utilizzare i filtri per intent insieme ai record di app per Android (AAR). L'incorporamento del record AAR all'interno di un messaggio NDEF garantisce che venga avviata solo l'applicazione legittima e la relativa attività di gestione NDEF. In questo modo, le applicazioni o le attività indesiderate non potranno leggere i dati di tag o i dati sul dispositivo altamente sensibili scambiati tramite NFC.
Rischio: NFC – mancanza di convalida dei messaggi NDEF
Quando un dispositivo con Android riceve dati da un tag NFC o da un dispositivo con NFC, il sistema attiva automaticamente l'applicazione o l'attività specifica configurata per gestire il messaggio NDEF contenuto al suo interno. In base alla logica implementata nell'applicazione, i dati contenuti nel tag o ricevuti dal dispositivo possono essere forniti ad altre attività per attivare ulteriori azioni, ad esempio l'apertura di pagine web.
Un'applicazione che non convalida il contenuto dei messaggi NDEF può consentire agli aggressori di utilizzare tag NFC o dispositivi con NFC per inserire payload dannosi all'interno dell'applicazione, causando un comportamento imprevisto che può comportare il download di file dannosi, command injection o DoS.
Mitigazioni
Prima di inviare il messaggio NDEF ricevuto a qualsiasi altro componente dell'applicazione, i dati al suo interno devono essere convalidati per verificare che siano nel formato previsto e che contengano le informazioni previste. In questo modo, i dati dannosi non vengono passati ad altri componenti dell'applicazione senza essere filtrati, riducendo il rischio di comportamenti imprevisti o attacchi che utilizzano dati NFC manomessi.
Il seguente snippet mostra un esempio di logica di convalida dei dati implementata come metodo con un messaggio NDEF come argomento e il relativo indice nell'array di messaggi. È stato implementato sull'esempio degli sviluppatori Android example per ottenere dati da un tag NDEF NFC scansionato:
Kotlin
//The method takes as input an element from the received NDEF messages array
fun isValidNDEFMessage(messages: Array<NdefMessage>, index: Int): Boolean {
// Checks if the index is out of bounds
if (index < 0 || index >= messages.size) {
return false
}
val ndefMessage = messages[index]
// Retrieves the record from the NDEF message
for (record in ndefMessage.records) {
// Checks if the TNF is TNF_ABSOLUTE_URI (0x03), if the Length Type is 1
if (record.tnf == NdefRecord.TNF_ABSOLUTE_URI && record.type.size == 1) {
// Loads payload in a byte array
val payload = record.payload
// Declares the Magic Number that should be matched inside the payload
val gifMagicNumber = byteArrayOf(0x47, 0x49, 0x46, 0x38, 0x39, 0x61) // GIF89a
// Checks the Payload for the Magic Number
for (i in gifMagicNumber.indices) {
if (payload[i] != gifMagicNumber[i]) {
return false
}
}
// Checks that the Payload length is, at least, the length of the Magic Number + The Descriptor
if (payload.size == 13) {
return true
}
}
}
return false
}
Java
//The method takes as input an element from the received NDEF messages array
public boolean isValidNDEFMessage(NdefMessage[] messages, int index) {
//Checks if the index is out of bounds
if (index < 0 || index >= messages.length) {
return false;
}
NdefMessage ndefMessage = messages[index];
//Retrieve the record from the NDEF message
for (NdefRecord record : ndefMessage.getRecords()) {
//Check if the TNF is TNF_ABSOLUTE_URI (0x03), if the Length Type is 1
if ((record.getTnf() == NdefRecord.TNF_ABSOLUTE_URI) && (record.getType().length == 1)) {
//Loads payload in a byte array
byte[] payload = record.getPayload();
//Declares the Magic Number that should be matched inside the payload
byte[] gifMagicNumber = {0x47, 0x49, 0x46, 0x38, 0x39, 0x61}; // GIF89a
//Checks the Payload for the Magic Number
for (int i = 0; i < gifMagicNumber.length; i++) {
if (payload[i] != gifMagicNumber[i]) {
return false;
}
}
//Checks that the Payload length is, at least, the length of the Magic Number + The Descriptor
if (payload.length == 13) {
return true;
}
}
}
return false;
}
Risorse
- Autorizzazioni di runtime
- Guide alla connettività
- Esempio
- bulkTransfer
- Crittografia
- Configurare il Bluetooth
- Nozioni di base su NFC
- Record di app per Android
- Specifica Bluetooth classica