Kategori OWASP: MASVS-PLATFORM: Interaksi Platform
Ringkasan
Menurut dokumentasi, ContentResolver
adalah “class yang memberikan akses aplikasi ke model konten”. ContentResolvers mengekspos metode untuk berinteraksi, mengambil, atau mengubah konten yang disediakan dari:
- Aplikasi terinstal (skema URI
content://
) - Sistem file (skema URI
file://
) - API pendukung yang disediakan oleh Android (skema URI
android.resource://
).
Singkatnya, kerentanan yang terkait dengan ContentResolver
termasuk dalam class deputi yang bingung karena penyerang dapat menggunakan hak istimewa aplikasi yang rentan untuk mengakses konten yang dilindungi.
Risiko: Penyalahgunaan berdasarkan URI file:// yang tidak tepercaya
Penyalahgunaan ContentResolver
yang menggunakan kerentanan URI file://
memanfaatkan kemampuan ContentResolver
untuk menampilkan deskriptor file yang dijelaskan oleh URI. Kerentanan ini memengaruhi fungsi seperti openFile()
, openFileDescriptor()
, openInputStream()
, openOutputStream()
, atau openAssetFileDescriptor()
dari API ContentResolver
. Kerentanan dapat disalahgunakan dengan URI file://
yang dikontrol sepenuhnya atau sebagian oleh penyerang untuk memaksa aplikasi mengakses file yang seharusnya tidak dapat diakses, seperti database internal atau preferensi bersama.
Salah satu kemungkinan skenario serangan adalah membuat galeri atau pemilih file berbahaya yang, saat digunakan oleh aplikasi yang rentan, akan menampilkan URI berbahaya.
Ada beberapa varian dari serangan ini:
- URI
file://
yang dikontrol sepenuhnya oleh penyerang dan mengarah ke file internal aplikasi - Bagian dari URI
file://
dikontrol oleh penyerang sehingga rentan terhadap path traversal - URI
file://
yang menargetkan link simbolis (symlink) yang dikontrol penyerang dan mengarah ke file internal aplikasi - Serupa dengan varian sebelumnya, tetapi di sini penyerang berulang kali menukar target symlink dari target yang sah ke file internal aplikasi. Tujuannya adalah untuk memanfaatkan kondisi race antara potensi pemeriksaan keamanan dan penggunaan jalur file
Dampak
Dampak dari pemanfaatan kerentanan ini bervariasi, bergantung pada tujuan penggunaan ContentResolver. Umumnya, hal ini dapat mengakibatkan data aplikasi yang dilindungi diambil atau dimodifikasi oleh pihak yang tidak diberi izin.
Mitigasi
Untuk mengurangi kerentanan ini, gunakan algoritma di bawah untuk memvalidasi deskriptor file. Setelah lulus validasi, deskriptor file dapat digunakan dengan aman.
Kotlin
fun isValidFile(ctx: Context, pfd: ParcelFileDescriptor, fileUri: Uri): Boolean {
// Canonicalize to resolve symlinks and path traversals.
val fdCanonical = File(fileUri.path!!).canonicalPath
val pfdStat: StructStat = Os.fstat(pfd.fileDescriptor)
// Lstat doesn't follow the symlink.
val canonicalFileStat: StructStat = Os.lstat(fdCanonical)
// Since we canonicalized (followed the links) the path already,
// the path shouldn't point to symlink unless it was changed in the
// meantime.
if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
return false
}
val sameFile =
pfdStat.st_dev == canonicalFileStat.st_dev &&
pfdStat.st_ino == canonicalFileStat.st_ino
if (!sameFile) {
return false
}
return !isBlockedPath(ctx, fdCanonical)
}
fun isBlockedPath(ctx: Context, fdCanonical: String): Boolean {
// Paths that should rarely be exposed
if (fdCanonical.startsWith("/proc/") ||
fdCanonical.startsWith("/data/misc/")) {
return true
}
// Implement logic to block desired directories. For example, specify
// the entire app data/ directory to block all access.
}
Java
boolean isValidFile(Context ctx, ParcelFileDescriptor pfd, Uri fileUri) {
// Canonicalize to resolve symlinks and path traversals
String fdCanonical = new File(fileUri.getPath()).getCanonicalPath();
StructStat pfdStat = Os.fstat(pfd.getFileDescriptor());
// Lstat doesn't follow the symlink.
StructStat canonicalFileStat = Os.lstat(fdCanonical);
// Since we canonicalized (followed the links) the path already,
// the path shouldn't point to symlink unless it was changed in the meantime
if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
return false;
}
boolean sameFile =
pfdStat.stDev == canonicalFileStat.stDev && pfdStat.stIno == canonicalFileStat.stIno;
if (!sameFile) {
return false;
}
return !isBlockedPath(ctx, fdCanonical);
}
boolean isBlockedPath(Context ctx, String fdCanonical) {
// Paths that should rarely be exposed
if (fdCanonical.startsWith("/proc/") || fdCanonical.startsWith("/data/misc/")) {
return true;
}
// Implement logic to block desired directories. For example, specify
// the entire app data/ directory to block all access.
}
Risiko: Penyalahgunaan berdasarkan URI content:// yang tidak tepercaya
Penyalahgunaan ContentResolver
yang menggunakan kerentanan URI content://
terjadi saat URI yang dikontrol sepenuhnya atau sebagian oleh penyerang diteruskan ke API ContentResolver
untuk beroperasi pada konten yang seharusnya tidak dapat diakses.
Ada dua skenario utama untuk serangan ini:
- Aplikasi beroperasi dengan konten internalnya sendiri. Misalnya: setelah mendapatkan URI dari penyerang, aplikasi email akan melampirkan data dari penyedia konten internalnya sendiri, bukan foto eksternal.
- Aplikasi bertindak sebagai proxy, lalu mengakses data aplikasi lain untuk penyerang. Misalnya: aplikasi email melampirkan data dari aplikasi X yang dilindungi oleh izin yang biasanya melarang penyerang melihat lampiran tersebut. Fungsi ini tersedia untuk aplikasi yang melakukan lampiran, tetapi pada awalnya tidak menyampaikan konten ini kepada penyerang.
Salah satu kemungkinan skenario serangan adalah membuat galeri atau pemilih file berbahaya yang, saat digunakan oleh aplikasi yang rentan, akan menampilkan URI berbahaya.
Dampak
Dampak dari pemanfaatan kerentanan ini bervariasi, bergantung pada konteks yang terkait dengan ContentResolver. Hal ini dapat mengakibatkan data aplikasi yang dilindungi diambil atau dimodifikasi oleh pihak yang tidak diberi izin.
Mitigasi
Umum
Memvalidasi URI yang masuk. Misalnya, menggunakan daftar yang diizinkan dari otoritas yang diizinkan dianggap sebagai praktik yang baik.
URI menargetkan penyedia konten yang tidak diekspor atau dilindungi dengan izin dan berasal dari aplikasi yang rentan
Periksa apakah URI menargetkan aplikasi Anda:
Kotlin
fun belongsToCurrentApplication(ctx: Context, uri: Uri): Boolean {
val authority: String = uri.authority.toString()
val info: ProviderInfo =
ctx.packageManager.resolveContentProvider(authority, 0)!!
return ctx.packageName.equals(info.packageName)
}
Java
boolean belongsToCurrentApplication(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return ctx.getPackageName().equals(info.packageName);
}
Atau jika penyedia yang ditargetkan diekspor:
Kotlin
fun isExported(ctx: Context, uri: Uri): Boolean {
val authority = uri.authority.toString()
val info: ProviderInfo =
ctx.packageManager.resolveContentProvider(authority, 0)!!
return info.exported
}
Java
boolean isExported(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return info.exported;
}
Atau jika izin eksplisit diberikan ke URI - pemeriksaan ini didasarkan pada asumsi bahwa jika izin eksplisit diberikan untuk mengakses data, URI tersebut tidak berbahaya:
Kotlin
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
val pid: Int = Process.myPid()
val uid: Int = Process.myUid()
return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
PackageManager.PERMISSION_GRANTED
}
Java
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
int pid = Process.myPid();
int uid = Process.myUid();
return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}
URI menargetkan ContentProvider yang dilindungi izin milik aplikasi lain yang memercayai aplikasi yang rentan.
Serangan ini relevan dengan situasi berikut:
- Ekosistem aplikasi tempat aplikasi menentukan dan menggunakan izin kustom atau mekanisme autentikasi lainnya.
- Serangan proxy izin, saat penyerang menyalahgunakan aplikasi rentan yang memiliki izin runtime, seperti READ_CONTACTS, untuk mengambil data dari penyedia sistem.
Uji apakah izin URI telah diberikan:
Kotlin
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
val pid: Int = Process.myPid()
val uid: Int = Process.myUid()
return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
PackageManager.PERMISSION_GRANTED
}
Java
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
int pid = Process.myPid();
int uid = Process.myUid();
return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}
Jika penggunaan penyedia konten lain tidak memerlukan pemberian izin, seperti saat aplikasi mengizinkan semua aplikasi dari ekosistem untuk mengakses semua data, maka larang penggunaan otoritas ini secara eksplisit.