دسته OWASP: MASVS-PLATFORM: پلتفرم تعامل
نمای کلی
طبق مستندات ، ContentResolver
«کلاسی است که برنامهها را به مدل محتوا دسترسی میدهد» . ContentResolvers روشهایی را برای تعامل، واکشی یا اصلاح محتوای ارائه شده از موارد زیر نشان میدهد:
- برنامه های نصب شده (طرح
content://
URI) - سیستم های فایل (طرح
file://
URI) - پشتیبانی از API های ارائه شده توسط Android (طرح
android.resource://
URI).
به طور خلاصه، آسیب پذیری های مربوط به ContentResolver
متعلق به کلاس معاون سردرگم است زیرا مهاجم می تواند از امتیازات یک برنامه آسیب پذیر برای دسترسی به محتوای محافظت شده استفاده کند.
خطر: سوء استفاده بر اساس فایل:// URI نامعتبر
سوء استفاده از ContentResolver
با استفاده از آسیبپذیری file://
URI از قابلیت ContentResolver
برای بازگرداندن توصیفگرهای فایل توصیفشده توسط URI سوء استفاده میکند. این آسیب پذیری بر توابعی مانند openFile()
، openFileDescriptor()
، openInputStream()
، openOutputStream()
یا openAssetFileDescriptor()
از ContentResolver
API تأثیر می گذارد. این آسیبپذیری را میتوان با یک file://
URI کاملاً یا جزئی تحت کنترل مهاجم مورد سوء استفاده قرار داد تا برنامه را مجبور به دسترسی به فایلهایی کند که در نظر گرفته نشده بودند، مانند پایگاههای داده داخلی یا ترجیحات مشترک.
یکی از سناریوهای حمله احتمالی ایجاد یک گالری مخرب یا انتخاب کننده فایل است که در صورت استفاده توسط یک برنامه آسیب پذیر، یک URI مخرب را برمی گرداند.
انواع کمی از این حمله وجود دارد:
-
file://
URI کاملاً توسط مهاجم کنترل می شود که به فایل های داخلی برنامه اشاره می کند - بخشی از
file://
URI توسط مهاجم کنترل می شود و آن را مستعد پیمایش مسیر می کند -
file://
URI با هدف قرار دادن پیوند نمادین (symlink) تحت کنترل مهاجم که به فایلهای داخلی برنامه اشاره میکند. - مشابه نوع قبلی است، اما در اینجا مهاجم به طور مکرر هدف 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.
}
خطر: سوء استفاده بر اساس محتوای نامعتبر:// URI
سوء استفاده از 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;
}
اگر استفاده از سایر ارائهدهندگان محتوا به مجوز نیاز ندارد - مانند زمانی که برنامه به همه برنامههای اکوسیستم اجازه دسترسی به همه دادهها را میدهد - استفاده از این مقامات را صریحاً ممنوع کنید.