Menyimpan JSON Web Token (JWT) atau refresh token dalam AsyncStorage adalah celah keamanan kritis pada aplikasi React Native. AsyncStorage beroperasi tanpa enkripsi bawaan: data disimpan dalam format file SQLite unencrypted atau XML plain text di sandbox aplikasi. Siapa pun yang memiliki akses fisik atau perantara ke perangkat dapat mengekstraksi kredensial sesi tanpa perlu membongkar payload kode.
Vektor Serangan: Ekstraksi Data dari Storage Plain Text
Dua vektor serangan umum yang mengeksploitasi penyimpanan token tanpa enkripsi adalah akses root filesystem dan ADB backup.
1. Root Filesystem Dump
Pada perangkat Android yang telah di-root atau perangkat iOS yang di-jailbreak, mekanisme isolasi sandbox aplikasi dapat ditembus. Penyerang dapat membaca direktori lokal secara langsung melalui shell:
adb shell
su
cat /data/data/com.aplikasi.anda/databases/RKStorage
# atau membaca file SharedPreferences default
cat /data/data/com.aplikasi.anda/shared_prefs/RKStorage.xmlToken autentikasi yang tersimpan di AsyncStorage langsung terekspos dalam bentuk teks biasa, memungkinkan pembajakan sesi (account takeover) tanpa interaksi korban.
2. Ekstraksi ADB Backup
Secara default, Android mengizinkan pencadangan data aplikasi via Android Debug Bridge (ADB) jika flag android:allowBackup bernilai true di AndroidManifest.xml. Penyerang cukup menghubungkan perangkat yang tidak terkunci ke komputer via USB:
adb backup -f backup.ab -noapk com.aplikasi.anda
# Konversi format Android Backup (.ab) ke tar archive
dd if=backup.ab bs=24 skip=1 | openssl zlib -d > backup.tar
tar -xvf backup.tarHasil ekstraksi tersebut mencakup basis data SQLite AsyncStorage secara utuh.
Arsitektur Hardware-Backed Security: Keystore & Keychain
Untuk memitigasi vektor ekstraksi, kunci enkripsi dan data kredensial harus didelegasikan ke subsistem keamanan berbasis perangkat keras.
- Android Keystore: Kunci kriptografi dibuat dan disimpan di dalam Trusted Execution Environment (TEE) atau StrongBox (chip HSM terpisah seperti Titan M). Operasi enkripsi/dekripsi dieksekusi di dalam secure domain. Kunci privat tidak pernah dimuat ke memori OS utama (Linux Kernel/Android runtime), sehingga memblokir ekstraksi oleh root exploit.
- iOS Keychain Services: Kredensial dienkripsi menggunakan mekanisme hardware crypto engine dari Secure Enclave. Melalui konfigurasi atribut
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, data hanya didekripsi saat perangkat telah dibuka kuncinya pertama kali pasca-reboot dan dibatasi agar tidak dapat dipindahkan ke perangkat lain via iCloud maupun unencrypted iTunes backup.
Implementasi Secure Storage Service
Gunakan library react-native-keychain untuk mengikat interaksi native Keystore dan Keychain ke React Native runtime.
npm install react-native-keychain
# atau
yarn add react-native-keychainImplementasikan storage service menggunakan TypeScript dengan penegakan level hardware security dan opsi fallback:
// src/services/SecureAuthStorage.ts
import * as Keychain from 'react-native-keychain';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface AuthTokens {
accessToken: string;
refreshToken: string;
}
const SERVICE_NAME = 'com.aplikasi.auth';
const LEGACY_STORAGE_KEY = '@auth_tokens_legacy';
export class SecureAuthStorage {
/**
* Menyimpan token dengan prioritas SECURE_HARDWARE (TEE/StrongBox).
*/
static async setTokens(tokens: AuthTokens): Promise {
const payload = JSON.stringify(tokens);
try {
const result = await Keychain.setGenericPassword('auth_session', payload, {
service: SERVICE_NAME,
accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
securityLevel: Keychain.SECURITY_LEVEL.SECURE_HARDWARE,
});
return Boolean(result);
} catch (error) {
// Graceful fallback jika hardware-backed Keystore tidak didukung perangkat
return this.setTokensFallback(payload);
}
}
/**
* Fallback penyimpanan ke software-backed Keystore.
*/
private static async setTokensFallback(payload: string): Promise {
try {
const fallbackResult = await Keychain.setGenericPassword('auth_session', payload, {
service: SERVICE_NAME,
accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
securityLevel: Keychain.SECURITY_LEVEL.SECURE_SOFTWARE,
});
return Boolean(fallbackResult);
} catch (fallbackError) {
console.error('Gagal menyimpan token ke secure storage:', fallbackError);
return false;
}
}
/**
* Mengambil token dengan mekanisme Silent Migration dari AsyncStorage lama.
*/
static async getTokens(): Promise {
try {
// 1. Cek kredensial di hardware storage terlebih dahulu
const credentials = await Keychain.getGenericPassword({
service: SERVICE_NAME,
});
if (credentials) {
return JSON.parse(credentials.password) as AuthTokens;
}
// 2. Jalankan silent migration jika hardware storage kosong
return await this.migrateFromLegacyStorage();
} catch (error) {
console.error('Gagal membaca token secure storage:', error);
return null;
}
}
/**
* Membaca AsyncStorage, migrasi ke Keychain, lalu menghapus data lama.
*/
private static async migrateFromLegacyStorage(): Promise {
try {
const legacyData = await AsyncStorage.getItem(LEGACY_STORAGE_KEY);
if (!legacyData) {
return null;
}
const parsedTokens = JSON.parse(legacyData) as AuthTokens;
// Amankan ke Keychain/Keystore
const migrated = await this.setTokens(parsedTokens);
if (migrated) {
// Hapus token plain text setelah migrasi sukses
await AsyncStorage.removeItem(LEGACY_STORAGE_KEY);
return parsedTokens;
}
return null;
} catch (migrationError) {
console.error('Migrasi data gagal:', migrationError);
return null;
}
}
/**
* Menghapus seluruh sesi autentikasi.
*/
static async clearTokens(): Promise {
try {
await AsyncStorage.removeItem(LEGACY_STORAGE_KEY);
return await Keychain.resetGenericPassword({ service: SERVICE_NAME });
} catch (error) {
console.error('Gagal menghapus token:', error);
return false;
}
}
} Menangani Kasus Edge: Fallback dan Fragmentasi Android
Spesifikasi perangkat Android sangat bervariasi. Perangkat murah atau model lama sering kali tidak memiliki implementasi StrongBox Keymaster atau bahkan TEE yang stabil.
- Deteksi Security Level: Jika pemanggilan dengan
Keychain.SECURITY_LEVEL.SECURE_HARDWAREmelempar error runtime (sepertiKeyStoreException), wrapper di atas secara otomatis mencobaKeychain.SECURITY_LEVEL.SECURE_SOFTWARE. Meskipun tidak seaman modul hardware, software-backed Keystore tetap mengenkripsi kredensial dengan kunci yang diproteksi oleh Android OS master key dan tidak dapat diekstrak lewat file transfer ADB standar. - Device Lock State: Atribut
ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLYmemastikan background service tidak gagal mengakses token saat perangkat menyala, namun tetap mengunci data jika perangkat di-clone atau datanya dicadangkan ke eksternal storage.
Verifikasi Proteksi Manifest
Selain mengamankan storage via kode, nonaktifkan fitur auto-backup sistem operasi Android pada android/app/src/main/AndroidManifest.xml untuk mencegah backup system-level membaca artefak aplikasi yang tidak terenkripsi:
<application
android:name=".MainApplication"
android:allowBackup="false"
android:fullBackupContent="false"
... >
</application>Kombinasi antara react-native-keychain berbasis TEE/Secure Enclave, strategi silent migration tanpa memutus sesi user, dan deaktivasi allowBackup menutup celah ekstraksi kredensial offline pada aplikasi React Native produksi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!