دسته OWASP: MASVS-PLATFORM: تعامل پلتفرم
نمای کلی
طبق مستندات ، ContentResolver «کلاسی است که به برنامهها امکان دسترسی به مدل محتوا را میدهد» . ContentResolverها متدهایی را برای تعامل، واکشی یا تغییر محتوای ارائه شده از موارد زیر ارائه میدهند:
- برنامههای نصبشده (
content://طرح URI) - سیستمهای فایل (طرح
file://URI) - پشتیبانی از API های ارائه شده توسط اندروید (طرح
android.resource://URI).
به طور خلاصه، آسیبپذیریهای مربوط به ContentResolver متعلق به کلاس confusion Deputy هستند زیرا مهاجم میتواند از امتیازات یک برنامه آسیبپذیر برای دسترسی به محتوای محافظتشده استفاده کند.
خطر: سوءاستفاده بر اساس آدرس فایل// نامعتبر
سوءاستفاده از ContentResolver با استفاده از آسیبپذیری file:// URI، از قابلیت ContentResolver برای بازگرداندن توصیفگرهای فایل توصیفشده توسط URI سوءاستفاده میکند. این آسیبپذیری بر توابعی مانند openFile() ، openFileDescriptor() ، openInputStream() ، openOutputStream() یا openAssetFileDescriptor() از API ContentResolver تأثیر میگذارد. این آسیبپذیری میتواند با یک URI file:// که بهطور کامل یا جزئی توسط مهاجم کنترل میشود، مورد سوءاستفاده قرار گیرد تا برنامه را مجبور به دسترسی به فایلهایی کند که قرار نبود در دسترس باشند، مانند پایگاههای داده داخلی یا تنظیمات برگزیده مشترک.
یکی از سناریوهای حملهی احتمالی، ایجاد یک گالری یا انتخابگر فایل مخرب است که در صورت استفاده توسط یک برنامهی آسیبپذیر، یک URI مخرب را برمیگرداند.
این حمله چند نوع مختلف دارد:
- آدرس اینترنتی
file://که کاملاً تحت کنترل مهاجم است و به فایلهای داخلی یک برنامه اشاره دارد. - بخشی از
file://URI توسط مهاجم کنترل میشود و آن را مستعد پیمایش مسیر میکند. - آدرس اینترنتی
file://که یک پیوند نمادین (symlink) تحت کنترل مهاجم را هدف قرار میدهد و به فایلهای داخلی برنامه اشاره دارد. - مشابه نوع قبلی، اما در اینجا مهاجم مکرراً هدف سیملینک را از یک هدف قانونی به فایلهای داخلی یک برنامه تغییر میدهد. هدف، سوءاستفاده از شرایط رقابتی بین بررسی امنیتی بالقوه و استفاده از مسیر فایل است.
تأثیر
تأثیر سوءاستفاده از این آسیبپذیری بسته به کاربرد ContentResolver متفاوت است. در بسیاری از موارد، میتواند منجر به استخراج دادههای محافظتشده یک برنامه یا تغییر دادههای محافظتشده توسط اشخاص غیرمجاز شود.
کاهشها
برای کاهش این آسیبپذیری، از الگوریتم زیر برای اعتبارسنجی توصیفگر فایل استفاده کنید. پس از قبولی در اعتبارسنجی، توصیفگر فایل میتواند با خیال راحت مورد استفاده قرار گیرد.
کاتلین
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.
}
جاوا
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.
}
خطر: سوءاستفاده بر اساس محتوای غیرقابل اعتماد: // آدرس اینترنتی
سوءاستفاده از ContentResolver با استفاده از آسیبپذیری content:// URI زمانی رخ میدهد که یک URI که بهطور کامل یا جزئی تحت کنترل مهاجم است، به APIهای ContentResolver منتقل شود تا روی محتوایی که قرار نبوده در دسترس باشد، عملیات انجام دهد.
دو سناریوی اصلی برای این حمله وجود دارد:
- این برنامه بر اساس محتوای داخلی خود عمل میکند. برای مثال: پس از دریافت یک URI از یک مهاجم، برنامه ایمیل به جای یک عکس خارجی، دادههایی را از ارائهدهنده محتوای داخلی خود پیوست میکند.
- این برنامه به عنوان یک پروکسی عمل میکند و سپس به دادههای برنامه دیگری برای مهاجم دسترسی پیدا میکند. به عنوان مثال: برنامه ایمیل، دادههایی را از برنامه X پیوست میکند که توسط مجوزی محافظت میشود که معمولاً مهاجم را از دیدن آن پیوست خاص منع میکند. این دادهها برای برنامهای که پیوست را انجام میدهد در دسترس است، اما در ابتدا این محتوا را به مهاجم منتقل نمیکند.
یکی از سناریوهای حملهی احتمالی، ایجاد یک گالری یا انتخابگر فایل مخرب است که در صورت استفاده توسط یک برنامهی آسیبپذیر، یک URI مخرب را برمیگرداند.
تأثیر
تأثیر سوءاستفاده از این آسیبپذیری بسته به زمینهی مرتبط با ContentResolver متفاوت است. این ممکن است منجر به استخراج دادههای محافظتشدهی یک برنامه یا تغییر دادههای محافظتشده توسط اشخاص غیرمجاز شود.
کاهشها
عمومی
اعتبارسنجی URI های ورودی. به عنوان مثال، استفاده از لیست مجوزهای مورد انتظار، یک روش خوب محسوب میشود.
URI، ارائهدهنده محتوای صادرنشده یا دارای مجوز را که متعلق به برنامه آسیبپذیر است، هدف قرار میدهد.
بررسی کنید که آیا URI برنامه شما را هدف قرار میدهد یا خیر:
کاتلین
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)
}
جاوا
boolean belongsToCurrentApplication(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return ctx.getPackageName().equals(info.packageName);
}
یا اگر ارائهدهندهی هدف صادر شود:
کاتلین
fun isExported(ctx: Context, uri: Uri): Boolean {
val authority = uri.authority.toString()
val info: ProviderInfo =
ctx.packageManager.resolveContentProvider(authority, 0)!!
return info.exported
}
جاوا
boolean isExported(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return info.exported;
}
یا اگر مجوز صریح به URI داده شود - این بررسی بر اساس این فرض است که اگر مجوز صریح برای دسترسی به دادهها داده شود، URI مخرب نیست:
کاتلین
// 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
}
جاوا
// 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 یک ContentProvider محافظتشده با مجوز را هدف قرار میدهد که متعلق به برنامهی دیگری است که به برنامهی آسیبپذیر اعتماد دارد.
این حمله مربوط به موقعیتهای زیر است:
- اکوسیستمهایی از برنامههای کاربردی که در آنها برنامهها مجوزهای سفارشی یا سایر مکانیسمهای احراز هویت را تعریف و استفاده میکنند.
- حملات پروکسی مجوز، که در آن مهاجم از یک برنامه آسیبپذیر که دارای مجوز زمان اجرا مانند READ_CONTACTS است، برای بازیابی دادهها از یک ارائهدهنده سیستم سوءاستفاده میکند.
بررسی کنید که آیا مجوز URI اعطا شده است یا خیر:
کاتلین
// 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
}
جاوا
// 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;
}
اگر استفاده از سایر ارائهدهندگان محتوا نیازی به مجوز ندارد - مانند زمانی که برنامه به همه برنامههای اکوسیستم اجازه دسترسی به همه دادهها را میدهد - صریحاً استفاده از این مجوزها را ممنوع کنید.