การเปลี่ยนเส้นทาง Intent

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

ภาพรวม

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

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

ผลกระทบ

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

การลดปัญหา

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

  • ล้างข้อมูลที่รวมไว้อย่างเหมาะสม โปรดอย่าลืมตรวจสอบ หรือล้างการแจ้งว่าไม่เหมาะสม (FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION, FLAG_GRANT_PERSISTABLE_URI_PERMISSION, and FLAG_GRANT_PREFIX_URI_PERMISSION) และตรวจสอบว่าระบบ เปลี่ยนเส้นทางเจตนาไปที่ใด IntentSanitizer ช่วยคุณในกระบวนการนี้ได้
  • ใช้ออบเจ็กต์ PendingIntent ซึ่งจะป้องกันไม่ให้มีการส่งออกคอมโพเนนต์และทําให้ Intent การดําเนินการเป้าหมายเปลี่ยนแปลงไม่ได้

แอปจะตรวจสอบได้ว่ามีการเปลี่ยนเส้นทาง Intent ไปที่ใดโดยใช้เมธอดต่างๆ เช่น ResolveActivity

Kotlin

val intent = getIntent()
// Get the component name of the nested intent.
val forward = intent.getParcelableExtra<Parcelable>("key") as Intent
val name: ComponentName = forward.resolveActivity(packageManager)
// Check that the package name and class name contain the expected values.
if (name.packagename == "safe_package" && name.className == "safe_class") {
    // Redirect the nested intent.
    startActivity(forward)
}

Java

Intent intent = getIntent()
// Get the component name of the nested intent.
Intent forward = (Intent) intent.getParcelableExtra("key");
ComponentName name = forward.resolveActivity(getPackageManager());
// Check that the package name and class name contain the expected values.
if (name.getPackageName().equals("safe_package") &&
        name.getClassName().equals("safe_class")) {
    // Redirect the nested intent.
    startActivity(forward);
}

แอปสามารถใช้ IntentSanitizer โดยใช้ตรรกะที่คล้ายกับ ต่อไปนี้

Kotlin

val intent = IntentSanitizer.Builder()
     .allowComponent("com.example.ActivityA")
     .allowData("com.example")
     .allowType("text/plain")
     .build()
     .sanitizeByThrowing(intent)

Java

Intent intent = new  IntentSanitizer.Builder()
     .allowComponent("com.example.ActivityA")
     .allowData("com.example")
     .allowType("text/plain")
     .build()
     .sanitizeByThrowing(intent);

การปกป้องเริ่มต้น

Android 16 เปิดตัวโซลูชันการปิดช่องโหว่ด้านความปลอดภัยโดยค่าเริ่มต้นเพื่อIntent การเจาะช่องโหว่จากการเปลี่ยนเส้นทาง ในกรณีส่วนใหญ่ แอปที่ใช้ Intent จะไม่พบปัญหาความเข้ากันได้

เลือกไม่ใช้การจัดการการเปลี่ยนเส้นทางตามความตั้งใจ

Android 16 เปิดตัว API ใหม่ที่อนุญาตให้แอปเลือกไม่ใช้การปกป้องความปลอดภัยในการเปิดตัว ซึ่งอาจจำเป็นในบางกรณีที่ลักษณะการทำงานด้านความปลอดภัยเริ่มต้น รบกวน Use Case ที่ถูกต้องของแอป

ใน Android 16 คุณสามารถเลือกไม่รับการป้องกันความปลอดภัยได้โดยใช้เมธอด removeLaunchSecurityProtection() ในออบเจ็กต์ Intent เช่น

val i = intent
val iSublevel: Intent? = i.getParcelableExtra("sub_intent")
iSublevel?.removeLaunchSecurityProtection() // Opt out from hardening
iSublevel?.let { startActivity(it) }

ความผิดพลาดที่พบบ่อย

  • ตรวจสอบว่า getCallingActivity() แสดงผลค่าที่ไม่ใช่ค่า Null หรือไม่ แอปที่เป็นอันตราย อาจให้ค่า Null สำหรับฟังก์ชันนี้
  • สมมติว่า checkCallingPermission() ทำงานในทุกบริบท หรือเมธอด จะโยนข้อยกเว้นเมื่อเมธอดนั้นส่งคืนจำนวนเต็ม

ฟีเจอร์การแก้ไขข้อบกพร่อง

สำหรับแอปที่กำหนดเป้าหมายเป็น Android 12 (ระดับ API 31) ขึ้นไป คุณสามารถเปิดใช้ฟีเจอร์การแก้ไขข้อบกพร่องซึ่งในบางกรณีจะช่วยตรวจหาว่าแอปเปิด Intent อย่างไม่ปลอดภัยหรือไม่

หากแอปของคุณดำเนินการทั้ง 2 อย่างต่อไปนี้ ระบบจะตรวจพบการเปิดใช้ Intent ที่ไม่ปลอดภัย และจะเกิดการละเมิดStrictMode

  • แอปของคุณยกเลิกการส่ง Intent ที่ซ้อนกันจากส่วนเสริมของ Intent ที่ส่งแล้ว
  • แอปของคุณจะเริ่มคอมโพเนนต์ของแอปทันทีโดยใช้ Intent ที่ซ้อนกันนั้น เช่น ส่ง Intent ไปยัง startActivity(), startService() หรือ bindService()

แหล่งข้อมูล