ความปลอดภัยของกิจกรรม

Android ปกป้องผู้ใช้จากแอปที่เป็นอันตรายและมอบประสบการณ์ UI ที่เชื่อถือได้ เฟรมเวิร์กการรักษาความปลอดภัยของกิจกรรมประกอบด้วยกฎและข้อจำกัดของแพลตฟอร์ม กฎและข้อจำกัดเหล่านี้ป้องกันการขัดจังหวะ UI ที่ไม่ต้องการ การลักลอบใช้กิจกรรม และภัยคุกคามด้านความปลอดภัยอื่นๆ ภัยคุกคามเหล่านี้เกี่ยวข้องกับเวลาและวิธีที่คอมโพเนนต์ของแอปปรากฏบนหน้าจอ คอมโพเนนต์หลักของเฟรมเวิร์กนี้จำกัดการเริ่มกิจกรรมจากเบื้องหลัง

ข้อจำกัดในการเปิดใช้กิจกรรมในเบื้องหลัง

การเปิดใช้กิจกรรมในเบื้องหลัง (BAL) จะเกิดขึ้นเมื่อแอปที่ไม่ได้ทำงานอยู่เบื้องหน้า ไม่มีกิจกรรมที่มองเห็นได้ หรือ PendingIntent ที่ได้รับจากแอปอื่นพยายามเริ่มกิจกรรมใหม่ นี่คือการเปิดใช้กิจกรรมในเบื้องหลัง (BAL) แม้ว่าจะมีกรณีการใช้งานที่ถูกต้องตามกฎหมาย เช่น เมื่อแอปนาฬิกาปลุกเริ่มทำงาน แต่ BAL ที่ไม่จำกัดจะทำให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ไม่ดีและสร้างช่องโหว่ด้านความปลอดภัย

เหตุใดจึงมีการจำกัด

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

  • การลักลอบใช้ UI และโฆษณาป๊อปอัป: แอปที่ทำงานอยู่เบื้องหลังเปิดใช้กิจกรรม (มักจะเป็นโฆษณา) เหนือแอปที่ผู้ใช้กำลังโต้ตอบอยู่โดยไม่คาดคิด ซึ่งเป็นการลักลอบใช้เซสชันของผู้ใช้
  • ฟิชชิงและการแอบอ้างเป็นผู้อื่น: แอปที่ทำงานอยู่เบื้องหลังเปิดใช้กิจกรรมที่แอบอ้างเป็นแอปอื่น (เช่น หน้าจอเข้าสู่ระบบปลอมสำหรับแอปที่ถูกต้องตามกฎหมาย) เพื่อขโมยข้อมูลเข้าสู่ระบบของผู้ใช้ ซึ่งมักทำได้ผ่านการโจมตีแบบ "Activity Sandwich" ซึ่งเป็นการแทรกกิจกรรมที่เป็นอันตรายลงในสแต็กงานของแอปที่ถูกต้องตามกฎหมาย
  • Tapjacking: แอปที่ทำงานอยู่เบื้องหลังจะแสดงกิจกรรมที่โปร่งใสหรือถูกบดบังไว้เหนือแอปอื่นเพื่อดักจับการแตะของผู้ใช้และหลอกให้ผู้ใช้ดำเนินการที่ไม่ต้องการ
  • การปลุกแอป: คอมโพเนนต์ที่ทำงานอยู่เบื้องหลังของแอปหนึ่งจะปลุกคอมโพเนนต์ที่ทำงานอยู่เบื้องหน้าของอีกแอปหนึ่งเพื่อเพิ่มเมตริกผู้ใช้ที่ใช้งานอยู่รายวันอย่างไม่ถูกต้องตามกฎหมาย

บริการที่ทำงานอยู่เบื้องหน้า (สำหรับงานที่กำลังดำเนินการอยู่)

หากแอปของคุณต้องทำงานที่ใช้เวลานานในเบื้องหลังซึ่งผู้ใช้ ต้องรับทราบ เช่น การเล่นเพลงหรือการติดตามการออกกำลังกาย คุณควร ใช้บริการที่ทำงานอยู่เบื้องหน้า บริการที่ทำงานอยู่เบื้องหน้าต้องแสดงการแจ้งเตือนแบบถาวรที่ผู้ใช้ปิดไม่ได้ การแจ้งเตือนนี้สามารถมีส่วนควบคุมแบบโต้ตอบ (เช่น ปุ่มเล่น/หยุดชั่วคราวสำหรับแอปเพลง) ซึ่งจะช่วยให้ผู้ใช้รับทราบข้อมูลและควบคุมได้ แต่จะไม่ขัดจังหวะผู้ใช้ด้วยกิจกรรมแบบเต็มหน้าจอ

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

กรณีที่อนุญาตให้เริ่มกิจกรรมในเบื้องหลัง (ข้อยกเว้น)

แอปจะเริ่มกิจกรรมจากเบื้องหลังได้หากเป็นไปตามเงื่อนไขข้อใดข้อหนึ่งต่อไปนี้

  • แอปมีหน้าต่างที่มองเห็นได้ เช่น กิจกรรมที่ทำงานอยู่เบื้องหน้า
  • แอปเป็นตัวแก้ไขวิธีการป้อนข้อมูล (IME) ที่ใช้อยู่ในปัจจุบัน
  • กิจกรรมเริ่มต้นจาก PendingIntent ที่ระบบส่งมา (เช่น จากการแตะการแจ้งเตือน)
  • แอปได้รับสิทธิ์ SYSTEM_ALERT_WINDOW จากผู้ใช้
  • แอปได้รับสิทธิ์ START_ACTIVITIES_FROM_BACKGROUND
  • แอปถูกผูกไว้กับบริการที่ได้รับสิทธิ์ให้เริ่มกิจกรรมในเบื้องหลัง
  • การเปิดใช้เริ่มต้นโดยแอปตัวเปิดของอุปกรณ์ เช่น เมื่อผู้ใช้แตะไอคอนแอปหรือโต้ตอบกับวิดเจ็ต
  • การเปิดใช้มาจากส่วนหลักของระบบปฏิบัติการที่ต้องทำงานตลอดเวลา เช่น บริการโทรศัพท์เริ่มหน้าจอสายเรียกเข้า

การปิดช่องโหว่ใหม่และการเลือกใช้ที่จำเป็น

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

ในกรณีส่วนใหญ่ แอปที่ส่ง PendingIntent ควรเป็นแอปที่เลือกใช้ เนื่องจากโดยปกติแล้วจะเป็นแอปที่ผู้ใช้โต้ตอบด้วยโดยตรง (เช่น การแตะปุ่ม)

ผู้ส่งต้องเลือกใช้ PendingIntent

เมื่อแอปของคุณกำหนดเป้าหมายเป็น Android 14 (ระดับ API 34) ขึ้นไป แอปจะไม่มอบสิทธิ์ BAL โดยค่าเริ่มต้นเมื่อส่ง PendingIntent อีกต่อไป หากคุณไม่ เลือกใช้โดยชัดแจ้ง ระบบอาจบล็อก การเปิดใช้กิจกรรม เว้นแต่ว่าผู้สร้าง ของ PendingIntent จะมอบสิทธิ์ของตนเองแล้ว

เพื่อให้การเปิดใช้สำเร็จ ผู้ส่งควรเลือกใช้เพื่อมอบสิทธิ์โดย เรียกใช้ ActivityOptions.setPendingIntentBackgroundActivityStartMode() และ โหมดที่แนะนำ คือ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE (เพิ่มใน SDK 36)

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

ตาราง PendingIntent
รูปที่ 1: ขั้นตอนการตัดสินใจสำหรับการเปิดใช้กิจกรรมในเบื้องหลัง

ใช้ ActivityOptions.setPendingIntentBackgroundActivityStartMode() เพื่อมอบ สิทธิ์

// Sender Side
ActivityOptions options = ActivityOptions.makeBasic()
    .setPendingIntentBackgroundActivityStartMode(
        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);

try {
    myPendingIntent.send(options.toBundle());
} catch (PendingIntent.CanceledException e) {
    Log.e(TAG, "The PendingIntent was canceled", e);
}
// Sender Side
val options = ActivityOptions.makeBasic().apply {
    pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE
}

try {
    myPendingIntent.send(options.toBundle())
} catch (e: PendingIntent.CanceledException) {
    Log.e(TAG, "The PendingIntent was canceled", e)
}

ผู้สร้างต้องเลือกใช้ PendingIntent

เมื่อแอปของคุณกำหนดเป้าหมายเป็น Android 15 (ระดับ API 35) ขึ้นไป แอปที่สร้าง PendingIntent จะไม่มอบสิทธิ์ในการเปิดใช้ในเบื้องหลังโดยค่าเริ่มต้นอีกต่อไป หากต้องการอนุญาตให้ผู้ส่งใช้สิทธิ์ BAL ของแอป คุณต้องเลือกใช้โดยชัดแจ้ง

ใช้ ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode() เพื่อ มอบสิทธิ์

// Creator Side
Intent intent = new Intent(context, MyActivity.class);
ActivityOptions options = ActivityOptions.makeBasic().setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);

PendingIntent pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE, options.toBundle());
// Creator Side
val intent = Intent(context, MyActivity::class.java)
val options = ActivityOptions.makeBasic().apply {
    pendingIntentCreatorBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}

val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent,
        PendingIntent.FLAG_IMMUTABLE, options.toBundle())

การเปิดใช้ด้วย IntentSender

ข้อจำกัด BAL เดียวกันนี้จะมีผลบังคับใช้ด้วยเมื่อเปิดใช้กิจกรรมโดยใช้ an IntentSender เนื่องจาก IntentSender ได้รับผ่าน PendingIntent.getIntentSender จึงต้องเป็นไปตามข้อกำหนดในการเลือกใช้ที่คล้ายกัน

  • ตั้งแต่ Android 14 (API 34) เป็นต้นไป การใช้ Context.startIntentSender() กำหนดให้ต้องเลือกใช้ฝั่งผู้ส่ง คุณต้องระบุบันเดิล ActivityOptions ที่นี่ด้วย
ActivityOptions options = ActivityOptions.makeBasic()
        .setPendingIntentBackgroundActivityStartMode(
            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);

context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
        flagsValues, extraFlags, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
    pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}

context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
        flagsValues, extraFlags, options.toBundle())
  • ตั้งแต่ Android 17 (API 37 ขึ้นไป) เป็นต้นไป การใช้ IntentSender.sendIntent() กำหนดให้ต้องเลือกใช้ฝั่งผู้ส่ง
ActivityOptions options = ActivityOptions.makeBasic()
        .setPendingIntentBackgroundActivityStartMode(
            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);

myIntentSender.sendIntent(context, code, intent, onFinished, handler,
        requiredPermission, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
    pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}

myIntentSender.sendIntent(context, code, intent, onFinished, handler,
        requiredPermission, options.toBundle())

แผนภาพลำดับงาน: ข้อจำกัด BAL

ตาราง PendingIntent
รูปที่ 2: กระบวนการเปิดใช้กิจกรรมอย่างปลอดภัยโดยใช้ PendingIntent

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

  1. การสร้างและการมอบสิทธิ์ (แอป ก - ผู้สร้าง)
    1. แอปผู้สร้างสร้าง PendingIntent
    2. หากกำหนดเป้าหมายเป็น SDK 35 ขึ้นไป ผู้สร้างต้องมอบสิทธิ์ BAL อย่างชัดแจ้งโดยใช้ setPendingIntentCreatorBackgroundActivityStartMode() หากต้องการให้มีการใช้สิทธิ์ โดยค่าเริ่มต้น ระบบจะไม่มอบสิทธิ์ใดๆ
    3. จากนั้นระบบจะส่ง PendingIntent ไปยังแอปอื่น (แอป ข)
  2. การเปิดใช้และการมีส่วนร่วม (แอป ข - ผู้ส่ง)
    1. ในภายหลัง แอปผู้ส่งจะเริ่มการเปิดใช้โดยเรียกใช้ PendingIntent.send()
    2. หากกำหนดเป้าหมายเป็น SDK 34 ขึ้นไป ผู้ส่งต้องมีส่วนร่วมในการมอบสิทธิ์ของตนเอง อย่างชัดแจ้งโดยใช้ setPendingIntentBackgroundActivityStartMode() หากต้องการให้มีการใช้สิทธิ์ โดยค่าเริ่มต้น ระบบจะไม่มอบสิทธิ์ใดๆ
  3. การตรวจสอบความปลอดภัยของระบบ Android
    1. ระบบ Android จะดักจับคำขอเปิดใช้และทำการตรวจสอบความปลอดภัย
    2. โดยจะประเมินเงื่อนไข 2 ข้อต่อไปนี้
    3. ผู้สร้างมอบสิทธิ์หรือไม่ และแอปผู้สร้าง เป็นไปตามข้อยกเว้น BAL ทั่วไปข้อใดข้อหนึ่งในปัจจุบันหรือไม่
    4. ผู้ส่งมีส่วนร่วมในการมอบสิทธิ์หรือไม่ และแอปผู้ส่ง เป็นไปตามข้อยกเว้น BAL ทั่วไปข้อใดข้อหนึ่งในปัจจุบันหรือไม่
  4. ผลลัพธ์
    1. อนุญาต: หากเป็นไปตามเงื่อนไขข้อใดข้อหนึ่ง จาก 2 ข้อในขั้นตอนที่ 3 ระบบจะตรวจสอบห่วงโซ่สิทธิ์ ระบบ Android จะเริ่มกิจกรรมเป้าหมาย และผู้ส่งจะได้รับผลลัพธ์ที่สำเร็จ
    2. บล็อก: หากไม่เป็นไปตามเงื่อนไขทั้ง 2 ข้อ ระบบจะบล็อกการเปิดใช้ แอปผู้ส่งจะไม่ได้รับค่าที่แสดงผลโดยตรงหรือข้อยกเว้นที่บ่งบอกถึงความล้มเหลว แต่ระบบ Android จะบันทึกข้อความ"Background activity launch blocked!" ลงใน Logcat ภายใน ซึ่งนักพัฒนาแอปต้องตรวจสอบเพื่อการแก้ไขข้อบกพร่อง

การป้องกันการลักลอบใช้กิจกรรม

Android 15 ได้นำกฎใหม่มาใช้สำหรับแอปที่กำหนดเป้าหมายเป็น API ระดับ 37 ขึ้นไป เพื่อป้องกันการโจมตีแบบลักลอบใช้กิจกรรม (เช่น "Activity Sandwich")

  • กฎข้อที่ 1: ภายในงานเดียว กิจกรรมจะเปิดใช้ได้โดยกิจกรรมอื่นที่อยู่ในแอปพลิเคชันเดียวกัน (นั่นคือมี UID เดียวกัน) กับกิจกรรมที่อยู่ด้านบนสุดในงานปัจจุบันเท่านั้น
  • กฎ 2: เฉพาะกิจกรรมภายในงานที่ทำงานอยู่เบื้องหน้าซึ่งมี UID ตรงกับ UID ของ กิจกรรมที่อยู่ด้านบนสุดเท่านั้นที่จะสร้างงานใหม่หรือนำงานอื่นที่มีอยู่มาไว้เบื้องหน้าได้

การเลือกใช้ของนักพัฒนาแอปสำหรับการป้องกันในงาน

คุณสามารถเปิดใช้ลักษณะการทำงานนี้ได้ตั้งแต่ SDK 37 ขึ้นไป โดยต้องเลือกใช้เพื่อเปิดใช้อย่างชัดแจ้ง ฟีเจอร์นี้ออกแบบมาเพื่อป้องกันการลักลอบใช้กิจกรรม (หรือ Activity Sandwiching) ซึ่งแอปที่เป็นอันตรายอาจเปิดใช้กิจกรรมในงานของแอปคุณเพื่อแอบอ้างเป็นแอปของคุณและขโมยข้อมูลผู้ใช้

การเปิดใช้การป้องกัน

หากต้องการเลือกใช้และเปิดใช้ ASM สำหรับแอปพลิเคชัน ให้ตั้งค่าแอตทริบิวต์ android:allowCrossUidActivitySwitchFromBelow เป็น "false" ในไฟล์ AndroidManifest.xml นี่เป็นการตั้งค่าระดับแอปพลิเคชันที่จะปกป้องกิจกรรมทั้งหมดในแอปของคุณโดยค่าเริ่มต้น

การสร้างข้อยกเว้นสำหรับกิจกรรมที่เฉพาะเจาะจง

หากคุณเปิดใช้การป้องกันนี้สำหรับแอป แต่ต้องการอนุญาตให้แอปอื่นเปิดใช้กิจกรรมที่เฉพาะเจาะจงและเชื่อถือได้ คุณสามารถสร้างข้อยกเว้นที่กำหนดเป้าหมายได้ หากต้องการยกเว้นกิจกรรมเดียวจากการป้องกันนี้ ให้เรียกใช้ setAllowCrossUidActivitySwitchFromBelow(true) ภายในเมธอด onCreate() ของกิจกรรมนั้น ซึ่งจะอนุญาตให้เปิดใช้กิจกรรมนั้นได้ในขณะที่กิจกรรมอื่นๆ ในแอปยังคงได้รับการป้องกัน

การแก้ปัญหา

กรอง Logcat เพื่อค้นหาข้อความที่เกี่ยวข้องโดยใช้นิพจน์ทั่วไป โดยมักจะใช้แท็ก ActivityTaskManager และการกรองตาม ActivityTaskManager จะช่วยแยกบันทึกได้

ทำความเข้าใจข้อความบันทึกที่สำคัญ

Blocked Launch (Error): ข้อความนี้บ่งบอกว่ามีการบล็อกการเริ่มกิจกรรม

เมื่อวิเคราะห์บันทึก ให้ตรวจสอบช่องต่อไปนี้

  • realCallingPackage: แอปที่ส่ง PendingIntent นี่คือผู้ส่ง
  • callingPackage: แอปที่สร้าง PendingIntent นี่คือผู้สร้าง

โหมดเข้มงวด

ตั้งแต่ Android 16 เป็นต้นไป นักพัฒนาแอปสามารถเปิดใช้โหมดเข้มงวดเพื่อรับการแจ้งเตือนเมื่อมีการบล็อกการเปิดใช้กิจกรรม (หรือมีความเสี่ยงที่จะถูกบล็อกเมื่อมีการเพิ่ม SDK เป้าหมายของแอป)

ตัวอย่างโค้ดเพื่อเปิดใช้จากเมธอด Application.onCreate() ของแอปพลิเคชัน กิจกรรม หรือคอมโพเนนต์อื่นๆ ของแอปพลิเคชันตั้งแต่เนิ่นๆ

override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     StrictMode.setVmPolicy(
         StrictMode.VmPolicy.Builder()
         .detectBlockedBackgroundActivityLaunch()
         .penaltyLog()
         .build());
     )
 }