รีโซลเวอร์เนื้อหา

หมวดหมู่ OWASP: MASVS-PLATFORM: การโต้ตอบกับแพลตฟอร์ม

ภาพรวม

ตามเอกสารประกอบ ContentResolver คือ "คลาสที่ให้สิทธิ์เข้าถึงโมเดลเนื้อหาแก่แอปพลิเคชัน" ContentResolvers เปิดเผยเมธอดเพื่อโต้ตอบ ดึงข้อมูล หรือแก้ไขเนื้อหาที่มาจาก:

  • `ContentResolver` มีเมธอดสำหรับการโต้ตอบ ดึง หรือแก้ไขเนื้อหาที่มาจากแอปที่ติดตั้ง (content:// URI Scheme)
  • ระบบไฟล์ (file:// URI Scheme)
  • API ที่รองรับตามที่ Android จัดหาให้ (android.resource:// URI Scheme)

สรุปได้ว่าช่องโหว่ที่เกี่ยวข้องกับ ContentResolver อยู่ในคลาส Confused Deputy เนื่องจากผู้โจมตีสามารถใช้สิทธิ์ของแอปพลิเคชันที่มีช่องโหว่เพื่อเข้าถึงเนื้อหาที่ได้รับการปกป้อง

ความเสี่ยง: การละเมิดตาม URI `file:// ` ที่ไม่น่าเชื่อถือ

การละเมิด ContentResolver โดยใช้ช่องโหว่ URI file:// จะใช้ประโยชน์จากความสามารถของ ContentResolver ในการแสดงตัวอธิบายไฟล์ที่อธิบายโดย URI ช่องโหว่นี้ส่งผลต่อฟังก์ชันต่างๆ เช่น openFile(), openFileDescriptor(), openInputStream(), openOutputStream(), หรือ openAssetFileDescriptor() จาก ContentResolver API ผู้โจมตีสามารถใช้ประโยชน์จากช่องโหว่นี้ด้วย URI file:// ที่ควบคุมโดยผู้โจมตีทั้งหมดหรือบางส่วนเพื่อบังคับให้แอปพลิเคชันเข้าถึงไฟล์ที่ไม่ได้มีไว้ให้เข้าถึงได้ เช่น ฐานข้อมูลภายในหรือค่ากำหนดที่แชร์

สถานการณ์การโจมตีที่เป็นไปได้วิธีหนึ่งคือการสร้างแกลเลอรีหรือเครื่องมือเลือกไฟล์ที่เป็นอันตราย ซึ่งเมื่อแอปที่มีช่องโหว่ใช้แล้ว จะแสดง URI ที่เป็นอันตราย

การโจมตีนี้มีหลายรูปแบบ ดังนี้

  • URI file:// ที่ควบคุมโดยผู้โจมตีทั้งหมดซึ่งชี้ไปยังไฟล์ภายในของแอป
  • URI file:// บางส่วนควบคุมโดยผู้โจมตี ทำให้เกิดการข้ามเส้นทางได้ง่าย
  • URI file:// ที่กำหนดเป้าหมายเป็นลิงก์สัญลักษณ์ (Symlink) ที่ควบคุมโดยผู้โจมตีซึ่งชี้ไปยังไฟล์ภายในของแอป
  • คล้ายกับรูปแบบก่อนหน้า แต่ในกรณีนี้ผู้โจมตีจะสลับเป้าหมายของ Symlink จากเป้าหมายที่ถูกต้องไปยังไฟล์ภายในของแอปซ้ำๆ เป้าหมายคือการใช้ประโยชน์จากภาวะแข่งขันระหว่างการตรวจสอบความปลอดภัยที่อาจเกิดขึ้นกับการใช้เส้นทางไฟล์

ผลกระทบ

ผลกระทบจากการใช้ประโยชน์จากช่องโหว่นี้จะแตกต่างกันไปขึ้นอยู่กับวัตถุประสงค์ในการใช้ ContentResolver ในหลายกรณี อาจส่งผลให้ข้อมูลที่ได้รับการปกป้องของแอปถูกกรองออกหรือมีการแก้ไขข้อมูลที่ได้รับการปกป้องโดยบุคคลที่ไม่ได้รับอนุญาต

การบรรเทา

หากต้องการบรรเทาช่องโหว่นี้ ให้ใช้อัลกอริทึมด้านล่างเพื่อตรวจสอบตัวบอกไฟล์ หลังจากผ่านการตรวจสอบแล้ว คุณจะใช้ตัวอธิบายไฟล์ได้อย่างปลอดภัย

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.
}


ความเสี่ยง: การละเมิดตาม URI `content:// ` ที่ไม่น่าเชื่อถือ

การละเมิด ContentResolver โดยใช้ช่องโหว่ URI content:// เกิดขึ้นเมื่อมีการส่ง URI ที่ควบคุมโดยผู้โจมตีทั้งหมดหรือบางส่วนไปยัง ContentResolver API เพื่อดำเนินการกับเนื้อหาที่ไม่ได้มีไว้ให้เข้าถึงได้

การโจมตีนี้มีสถานการณ์หลักๆ 2 สถานการณ์ ได้แก่

  • แอปดำเนินการกับเนื้อหาภายในของตัวเอง เช่น หลังจากได้รับ URI จากผู้โจมตีแล้ว แอปอีเมลจะแนบข้อมูลจากผู้ให้บริการเนื้อหาภายในของตัวเองแทนที่จะเป็นรูปภาพภายนอก
  • แอปทำหน้าที่เป็นพร็อกซีแล้วเข้าถึงข้อมูลของแอปพลิเคชันอื่นให้ผู้โจมตี เช่น แอปพลิเคชันอีเมลจะแนบข้อมูลจากแอป X ที่ได้รับการปกป้องด้วยสิทธิ์ซึ่งโดยปกติแล้วจะไม่อนุญาตให้ผู้โจมตีเห็นสิ่งที่แนบมานั้น แอปพลิเคชันที่แนบข้อมูลมีสิทธิ์เข้าถึงข้อมูลดังกล่าว แต่ในตอนแรกไม่มีสิทธิ์ จึงส่งต่อเนื้อหานี้ไปยังผู้โจมตี

สถานการณ์การโจมตีที่เป็นไปได้วิธีหนึ่งคือการสร้างแกลเลอรีหรือเครื่องมือเลือกไฟล์ที่เป็นอันตราย ซึ่งเมื่อแอปที่มีช่องโหว่ใช้แล้ว จะแสดง URI ที่เป็นอันตราย

ผลกระทบ

ผลกระทบจากการใช้ประโยชน์จากช่องโหว่นี้จะแตกต่างกันไปขึ้นอยู่กับบริบทที่เชื่อมโยงกับ ContentResolver ซึ่งอาจส่งผลให้ข้อมูลที่ได้รับการปกป้องของแอปถูกกรองออกหรือมีการแก้ไขข้อมูลที่ได้รับการปกป้องโดยบุคคลที่ไม่ได้รับอนุญาต

การบรรเทา

ทั่วไป

ตรวจสอบ URI ขาเข้า เช่น การใช้รายการที่อนุญาตของ Authority ที่คาดไว้ถือเป็นแนวทางปฏิบัติแนะนำ

URI กำหนดเป้าหมายเป็นผู้ให้บริการเนื้อหาที่ไม่ได้ส่งออกหรือได้รับการปกป้องด้วยสิทธิ์ซึ่งเป็นของผู้ให้บริการเนื้อหาที่ไม่ได้ส่งออกหรือได้รับการปกป้องด้วยสิทธิ์ซึ่งเป็นของแอปที่มีช่องโหว่

ตรวจสอบว่า URI กำหนดเป้าหมายเป็นแอปของคุณหรือไม่

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);
}

หรือหากผู้ให้บริการเป้าหมายได้รับการส่งออก

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;
}

หรือหากได้รับสิทธิ์ที่ชัดเจนสำหรับ URI - การตรวจสอบนี้อิงตามสมมติฐานว่าหากได้รับสิทธิ์ที่ชัดเจนในการเข้าถึงข้อมูล แสดงว่า URI ไม่เป็นอันตราย

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 กำหนดเป้าหมายเป็น ContentProvider ที่ได้รับการปกป้องด้วยสิทธิ์ซึ่งเป็นของแอปอื่นที่เชื่อถือแอปที่มีช่องโหว่

การโจมตีนี้เกี่ยวข้องกับสถานการณ์ต่อไปนี้

  • ระบบนิเวศของแอปพลิเคชันที่แอปกำหนดและใช้สิทธิ์ที่กำหนดเองหรือกลไกการตรวจสอบสิทธิ์อื่นๆ
  • การโจมตีพร็อกซีสิทธิ์ ซึ่งผู้โจมตีใช้ประโยชน์จากแอปที่มีช่องโหว่ซึ่งมีสิทธิ์รันไทม์ เช่น READ_CONTACTS เพื่อดึงข้อมูลจากผู้ให้บริการระบบ

ทดสอบว่ามีการให้สิทธิ์ URI แล้วหรือไม่

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;
}

หากการใช้ผู้ให้บริการเนื้อหารายอื่นไม่จำเป็นต้องมีการให้สิทธิ์ เช่น เมื่อแอปอนุญาตให้แอปทั้งหมดจากระบบนิเวศเข้าถึงข้อมูลทั้งหมด ให้ห้ามการใช้ Authority เหล่านี้อย่างชัดเจน