Bảo mật hoạt động

Android bảo vệ người dùng khỏi các ứng dụng độc hại và mang đến trải nghiệm giao diện người dùng đáng tin cậy. Khung bảo mật hoạt động bao gồm các quy tắc và hạn chế về nền tảng. Những quy tắc và hạn chế này ngăn chặn các gián đoạn không mong muốn về giao diện người dùng, hành vi chiếm đoạt tác vụ và các mối đe doạ bảo mật khác. Những mối đe doạ này liên quan đến thời điểm và cách các thành phần ứng dụng xuất hiện trên màn hình. Một thành phần chính của khung này hạn chế việc bắt đầu các hoạt động ở chế độ nền.

Hạn chế khi khởi chạy hoạt động ở chế độ nền

Hoạt động khởi chạy ở chế độ nền (BAL) xảy ra khi một ứng dụng không ở nền trước, không có hoạt động nào hiển thị hoặc PendingIntent nhận được từ một ứng dụng khác cố gắng bắt đầu một hoạt động mới. Đây là một sự kiện Khởi chạy hoạt động trong nền (BAL). Mặc dù có các trường hợp sử dụng hợp pháp, chẳng hạn như khi ứng dụng đồng hồ báo thức khởi động, nhưng BAL không hạn chế sẽ mang lại trải nghiệm không tốt cho người dùng và tạo ra các lỗ hổng bảo mật.

Tại sao các tài khoản này bị hạn chế?

Kể từ Android 10 (cấp độ API 29), nền tảng đã đặt ra các hạn chế về thời điểm ứng dụng có thể bắt đầu hoạt động ở chế độ nền. Các biện pháp bảo vệ này giúp ngăn chặn hành vi độc hại của ứng dụng và cải thiện trải nghiệm người dùng bằng cách giảm thiểu các hành vi sai trái thường gặp, bao gồm:

  • Tấn công giao diện người dùng và quảng cáo dạng cửa sổ: Một ứng dụng chạy nền bất ngờ khởi chạy một hoạt động (thường là quảng cáo) trên ứng dụng mà người dùng hiện đang tương tác, chiếm đoạt phiên của họ.
  • Tấn công giả mạo và mạo danh: Một ứng dụng chạy dưới nền khởi chạy một hoạt động mạo danh một ứng dụng khác (ví dụ: màn hình đăng nhập giả mạo cho một ứng dụng hợp pháp) để đánh cắp thông tin đăng nhập của người dùng. Điều này thường được thực hiện thông qua một cuộc tấn công "Activity Sandwich", trong đó một hoạt động độc hại được chèn vào ngăn xếp tác vụ của một ứng dụng hợp pháp.
  • Tấn công bằng cách chèn lớp phủ: Một ứng dụng chạy trong nền hiển thị một hoạt động trong suốt hoặc bị che khuất trên một ứng dụng khác để chặn các lượt nhấn của người dùng và lừa họ thực hiện các hành động không mong muốn.
  • Đánh thức ứng dụng: Một thành phần nền của một ứng dụng đánh thức các thành phần nền trước của một ứng dụng khác để tăng chỉ số người dùng hoạt động hằng ngày một cách bất hợp pháp.

Dịch vụ trên nền trước (cho các tác vụ đang diễn ra)

Nếu ứng dụng của bạn cần thực hiện một tác vụ chạy trong thời gian dài ở chế độ nền mà người dùng cần biết, chẳng hạn như phát nhạc hoặc theo dõi hoạt động tập luyện, thì bạn nên sử dụng Dịch vụ trên nền trước. Dịch vụ trên nền trước phải hiển thị một thông báo liên tục mà người dùng không thể đóng. Thông báo này có thể cung cấp các chế độ điều khiển tương tác (ví dụ: nút phát/tạm dừng cho ứng dụng nhạc). Điều này giúp người dùng nắm được thông tin và kiểm soát được nhưng không làm gián đoạn họ bằng một hoạt động toàn màn hình.

Bằng cách tuân theo hệ thống phân cấp này (bắt đầu bằng thông báo tiêu chuẩn và chỉ chuyển sang các lựa chọn xâm nhập hơn khi cần thiết), bạn sẽ tạo ra trải nghiệm tốt hơn và dễ đoán hơn cho người dùng.

Khi các lượt bắt đầu hoạt động trong nền được cho phép (trường hợp ngoại lệ)

Ứng dụng có thể bắt đầu một hoạt động ở chế độ nền nếu đáp ứng một trong các điều kiện sau:

  • Ứng dụng có một cửa sổ hiển thị, chẳng hạn như một hoạt động ở nền trước.
  • Ứng dụng này là Trình chỉnh sửa phương thức nhập (IME) hiện tại.
  • Hoạt động này bắt đầu từ một PendingIntent do hệ thống gửi (ví dụ: từ một lượt nhấn vào thông báo).
  • Người dùng đã cấp quyền SYSTEM_ALERT_WINDOW cho ứng dụng.
  • Ứng dụng đã được cấp quyền START_ACTIVITIES_FROM_BACKGROUND.
  • Ứng dụng bị ràng buộc bởi một dịch vụ đã được cấp quyền bắt đầu các hoạt động trong nền.
  • Trình chạy của thiết bị sẽ khởi động quá trình này, chẳng hạn như khi người dùng nhấn vào biểu tượng ứng dụng hoặc tương tác với một tiện ích.
  • Lần khởi chạy này là từ một phần cốt lõi của hệ điều hành phải luôn chạy, chẳng hạn như dịch vụ Điện thoại bắt đầu màn hình cuộc gọi đến.

Các biện pháp tăng cường bảo mật mới và lựa chọn chọn sử dụng bắt buộc

Để tăng cường bảo mật hơn nữa, Android đã đưa ra các quy tắc nghiêm ngặt hơn, yêu cầu người dùng phải chọn sử dụng một cách rõ ràng đối với những ứng dụng dùng PendingIntentIntentSender để khởi chạy các hoạt động. Chỉ được phép khởi chạy nếu ứng dụng đã tạo PendingIntent hoặc ứng dụng gửi PendingIntent chọn tham gia để cấp đặc quyền khởi chạy ở chế độ nền.

Trong hầu hết các trường hợp, ứng dụng gửi PendingIntent phải là ứng dụng chọn nhận, vì đây thường là ứng dụng mà người dùng đang tương tác trực tiếp (ví dụ: nhấn vào một nút).

Người gửi phải chọn sử dụng PendingIntent

Khi ứng dụng của bạn nhắm đến Android 14 (cấp độ API 34) trở lên, ứng dụng sẽ không còn cấp đặc quyền BAL theo mặc định khi gửi PendingIntent. Nếu bạn không chọn tham gia một cách rõ ràng, thì hoạt động khởi chạy có thể bị chặn, trừ phi người tạo PendingIntent đã cấp đặc quyền riêng.

Để đảm bảo quá trình phát hành thành công, người gửi nên chọn tham gia để cấp đặc quyền bằng cách gọi ActivityOptions.setPendingIntentBackgroundActivityStartMode()chế độ được đề xuấtActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE (được thêm vào SDK 36).

Đây là chế độ nghiêm ngặt và an toàn hơn. Quyền này chỉ được cấp nếu ứng dụng gửi đang hiển thị trên màn hình tại thời điểm gửi PendingIntent. Điều này đảm bảo chắc chắn hơn rằng hoạt động khởi chạy là kết quả trực tiếp của việc người dùng tương tác với ứng dụng của bạn.

Bảng ý định đang chờ xử lý
Hình 1: Luồng quyết định cho các lần khởi chạy hoạt động ở chế độ nền.

Sử dụng ActivityOptions.setPendingIntentBackgroundActivityStartMode() để cấp đặc quyền.

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

Nhà sáng tạo phải chọn sử dụng PendingIntent

Khi ứng dụng của bạn nhắm đến Android 15 (cấp độ API 35) trở lên, theo mặc định, một ứng dụng tạo một PendingIntent sẽ không còn cấp đặc quyền khởi chạy ở chế độ nền nữa. Để cho phép người gửi sử dụng các đặc quyền BAL của ứng dụng, bạn phải chọn sử dụng một cách rõ ràng.

Sử dụng ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode() để cấp đặc quyền.

// 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())

Khởi chạy bằng IntentSender

Các quy tắc hạn chế BAL tương tự cũng áp dụng khi khởi chạy các hoạt động bằng IntentSender. Vì IntentSender được lấy thông qua PendingIntent.getIntentSender, nên IntentSender phải tuân theo các yêu cầu tương tự về việc chọn sử dụng.

  • Kể từ Android 14 (API 34), việc sử dụng Context.startIntentSender() yêu cầu người gửi chọn sử dụng. Bạn cũng phải cung cấp gói ActivityOptions tại đây.
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())
  • Kể từ Android 17 (API 37 trở lên), việc sử dụng IntentSender.sendIntent() yêu cầu phải có lựa chọn chọn sử dụng ở phía người gửi.
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())

Sơ đồ trình tự: Các hạn chế về BAL

Bảng ý định đang chờ xử lý
Hình 2: Quy trình khởi chạy một hoạt động một cách an toàn bằng PendingIntent

Sơ đồ này minh hoạ quy trình khởi chạy một hoạt động một cách an toàn bằng cách sử dụng PendingIntent. Việc khởi chạy thành công phụ thuộc vào một chuỗi đặc quyền hợp lệ, trong đó ít nhất một trong các ứng dụng tham gia vừa cấp đặc quyền vừa có khả năng khởi chạy một hoạt động ở chế độ nền

  1. Tạo và uỷ quyền (Ứng dụng A – Người sáng tạo)
    1. Ứng dụng Creator tạo ra PendingIntent
    2. Nếu nhắm đến SDK 35 trở lên, người tạo phải uỷ quyền rõ ràng các đặc quyền BAL bằng cách sử dụng setPendingIntentCreatorBackgroundActivityStartMode() nếu muốn sử dụng các đặc quyền đó. Theo mặc định, không có đặc quyền nào được uỷ quyền.
    3. Sau đó, PendingIntent sẽ được chuyển đến một ứng dụng khác (Ứng dụng B)
  2. Khởi chạy và đóng góp (Ứng dụng B – Người gửi)
    1. Sau đó, ứng dụng Người gửi sẽ bắt đầu quá trình khởi chạy bằng cách gọi PendingIntent.send().
    2. Nếu nhắm đến SDK 34 trở lên, người gửi phải đóng góp rõ ràng các đặc quyền của riêng mình bằng cách sử dụng setPendingIntentBackgroundActivityStartMode() nếu muốn sử dụng các đặc quyền đó. Theo mặc định, không có đặc quyền nào được uỷ quyền.
  3. Xác thực tính bảo mật của hệ thống Android
    1. Hệ thống Android chặn yêu cầu khởi chạy và thực hiện quy trình kiểm tra bảo mật.
    2. Hàm này đánh giá 2 điều kiện:
    3. Nhà sáng tạo có uỷ quyền các đặc quyền của mình hay KHÔNG, VÀ ứng dụng Nhà sáng tạo hiện có đáp ứng một trong những trường hợp ngoại lệ chung theo BAL hay không?
    4. Người gửi có đóng góp các đặc quyền của mình hay không VÀ ứng dụng Người gửi hiện có đáp ứng một trong những trường hợp ngoại lệ chung về BAL hay không?
  4. Kết quả
    1. ĐƯỢC PHÉP: Nếu ít nhất một trong hai điều kiện ở bước 3 được đáp ứng, thì chuỗi đặc quyền sẽ được xác thực. Hệ thống Android sẽ bắt đầu hoạt động đích và người gửi sẽ nhận được kết quả thành công.
    2. BLOCKED: nếu không đáp ứng điều kiện nào, hệ thống sẽ chặn việc khởi chạy. Ứng dụng người gửi không nhận được giá trị trả về trực tiếp hoặc ngoại lệ cho biết lỗi. Thay vào đó, Hệ thống Android sẽ ghi nội bộ thông báo "Đã chặn hoạt động chạy ở chế độ nền!" vào Logcat. Nhà phát triển phải kiểm tra thông báo này để gỡ lỗi.

Ngăn chặn hành vi chiếm đoạt tác vụ

Để ngăn chặn các cuộc tấn công chiếm đoạt trong tác vụ (chẳng hạn như "Activity Sandwich"), Android 15 sẽ giới thiệu các quy tắc mới cho những ứng dụng nhắm đến API cấp 37 trở lên.

  • Quy tắc 1: Trong một tác vụ duy nhất, một hoạt động chỉ có thể được khởi chạy bởi một hoạt động khác thuộc cùng một ứng dụng (tức là có cùng UID) với hoạt động trên cùng hiện tại trong tác vụ.
  • Quy tắc 2: Chỉ hoạt động trong một tác vụ ở nền trước khớp với UID của hoạt động trên cùng mới được phép tạo một tác vụ mới hoặc đưa một tác vụ khác hiện có lên nền trước.

Nhà phát triển chọn sử dụng các biện pháp bảo vệ trong tác vụ

Bạn có thể bật hành vi này bắt đầu từ SDK mục tiêu 37, bạn phải chọn bật một cách rõ ràng. Chế độ này được thiết kế để ngăn chặn tình trạng chiếm đoạt trong tác vụ (hoặc Hoạt động Sandwiching), trong đó một ứng dụng độc hại có thể chạy một hoạt động vào tác vụ của ứng dụng để mạo danh ứng dụng và đánh cắp dữ liệu người dùng.

Bật các biện pháp bảo vệ

Để chọn sử dụng và bật ASM cho ứng dụng, hãy đặt thuộc tính android:allowCrossUidActivitySwitchFromBelow thành false trong tệp AndroidManifest.xml. Đây là một chế độ cài đặt ở cấp ứng dụng, theo mặc định sẽ bảo vệ tất cả hoạt động trong ứng dụng của bạn.

Tạo ngoại lệ cho các hoạt động cụ thể

Nếu đã bật tính năng này cho ứng dụng của mình nhưng cần cho phép các ứng dụng khác chạy một hoạt động cụ thể, đáng tin cậy, thì bạn có thể tạo một ngoại lệ có mục tiêu. Để miễn trừ một hoạt động khỏi biện pháp bảo vệ này, hãy gọi setAllowCrossUidActivitySwitchFromBelow(true) trong phương thức onCreate() của hoạt động đó. Điều này cho phép một hoạt động được khởi chạy trong khi phần còn lại của ứng dụng vẫn được bảo vệ.

Khắc phục sự cố

Lọc Logcat để tìm thông điệp liên quan bằng biểu thức chính quy. Thẻ ActivityTaskManager thường được dùng và việc lọc theo ActivityTaskManager có thể giúp tách biệt các nhật ký.

Tìm hiểu về các thông báo nhật ký chính

Chặn hoạt động khởi chạy (Lỗi): Thông báo này cho biết hoạt động bắt đầu đã bị chặn.

Khi phân tích nhật ký, hãy kiểm tra các trường sau:

  • realCallingPackage: Ứng dụng đã gửi PendingIntent. Đây là người gửi.
  • callingPackage: Ứng dụng đã tạo PendingIntent. Đây là nhà sáng tạo.

Chế độ nghiêm ngặt

Kể từ Android 16, nhà phát triển ứng dụng có thể bật Chế độ nghiêm ngặt để nhận thông báo khi một hoạt động khởi chạy bị chặn (hoặc có nguy cơ bị chặn khi SDK mục tiêu của ứng dụng được nâng cấp).

Mã ví dụ để bật từ sớm trong phương thức Application.onCreate() của Ứng dụng, Hoạt động hoặc thành phần ứng dụng khác:

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