1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-android-app-sec-guidebook

Клонировать/Скачать
4.1.3.md 25 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 27.11.2024 20:24 c13bfb7

4.1.3. Расширенные темы

4.1.3.1 Комбинации экспортируемых атрибутов и фильтров намерений (для активности)

Мы уже объяснили, как реализовать четыре типа активностей в этом руководстве: приватные активности, публичные активности, партнёрские активности и внутренние активности. В таблице ниже определены допустимые настройки для каждого типа экспортируемого атрибута активности и различные комбинации элементов intent-filter, которые они определяют в файле AndroidManifest.xml. Используйте созданную вами активность, чтобы проверить совместимость экспортируемых атрибутов и элементов intent-filter.

Значение экспортируемого атрибута
True False
Определён фильтр намерений Публичный (Не используется)
Фильтр намерений не определён Публичный, партнёрский, внутренний AndroidManifest.xml

Таблица 4.1-2

Если атрибут экспорта активности не указан, то активность является публичной, если у неё есть фильтр намерений, и приватной, если фильтра намерений нет [4]. Однако в данном руководстве запрещено устанавливать атрибут экспорта как «не указан». Как правило, лучше избегать зависимости от поведения по умолчанию любого данного API, как было сказано ранее. Кроме того, если существует явный способ (например, атрибут экспорта) для включения важных настроек, связанных с безопасностью, использование этих методов всегда является хорошей идеей.

Если определён какой-либо фильтр намерений, активность является публичной; в противном случае она является приватной. Дополнительную информацию см. на сайте https://developer.android.com/guide/topics/manifest/activity-element.html#exported.

Не следует использовать неопределённый фильтр намерений и атрибут экспорта false, так как в Android есть уязвимости, и из-за принципа работы фильтра намерений другие приложения могут случайно вызвать его. Два следующих рисунка иллюстрируют это объяснение. На рисунке 4.1–4 показан пример нормального поведения, где приватная активность (приложение A) может быть вызвана только через неявное намерение из того же приложения. Фильтр намерений (action ="X") определён для работы только внутри приложения A, поэтому это ожидаемое поведение.

На следующем рисунке 4.1–5 показан сценарий, в котором в приложениях B и A определены одинаковые фильтры намерений (action ="X"). Приложение A пытается вызвать приватную активность в том же приложении через неявное намерение, но на этот раз отображается диалоговое окно, запрашивающее у пользователя выбор приложения, а также публичная активность B-1 в приложении B, которая вызывается ошибочно из-за выбора пользователя. Из-за этой уязвимости конфиденциальная информация может быть отправлена другим приложениям или приложение может получить неожиданные возвращаемые значения.

Как показано выше, использование фильтра намерений для отправки неявного намерения в приватное приложение может привести к неожиданному поведению, поэтому лучше избегать этой настройки. Кроме того, мы подтвердили, что такое поведение не зависит от порядка установки приложений A и B.

4.1.3.2 Проверка запрашивающего приложения

Здесь мы объясняем некоторые технические сведения о том, как реализовать партнёрскую активность. Партнёрские приложения разрешают доступ только зарегистрированным в белом списке конкретным приложениям, и все остальные приложения отклоняются. Поскольку другим приложениям также требуется доступ, мы не можем использовать разрешения подписи для контроля доступа.

Короче говоря, мы хотим проверить приложение, пытающееся использовать партнёрскую активность, проверив, зарегистрировано ли оно в предопределённом белом списке; если да, доступ разрешается, если нет — доступ отклоняется. Способ проверки приложения заключается в получении сертификата от запрашивающего доступ приложения и сравнении его с белым списком.

Некоторые разработчики могут подумать, что достаточно сравнить название пакета программного обеспечения, не получая сертификат, но легко подделать название пакета законного приложения, поэтому это не лучший способ проверки подлинности. Значения, указанные произвольно, не применяются к аутентификации. С другой стороны, поскольку только разработчики приложения имеют ключ разработчика, используемый для подписи сертификатов, это лучший метод идентификации. Поскольку сертификаты трудно подделать, за исключением случаев, когда злоумышленник может украсть ключ разработчика, вероятность того, что вредоносное приложение будет доверено, невелика. Хотя можно сохранить весь сертификат в белом списке, для уменьшения размера файла достаточно сохранить значение SHA-256 хэша.

Этот метод имеет два ограничения:

  • Запрашивающее приложение должно использовать startActivityForResult(), а не startActivity().
  • Запрашивающее приложение должно вызывать только из Activity.

Второе ограничение является следствием первого, поэтому технически существует только одно ограничение.

Это ограничение возникает из-за ограничения метода Activity.getCallingPackage(), который возвращает имя пакета вызывающего приложения. Метод Activity.getCallingPackage() возвращает имя исходного (запрашивающего) приложения только при вызове startActivityForResult(), но, к сожалению, возвращает null при вызове из startActivity(). Поэтому при использовании этого метода объяснения исходное (запрашивающее) приложение должно использовать startActivityForResult(), даже если ему не нужно получать возвращаемое значение. Кроме того, startActivityForResult() можно использовать только в классе Activity, поэтому исходное приложение ограничено активностью. GET_TASKS Permission

<uses-permission android:name="android.permission.GET_TASKS" />

<application
    android:allowBackup="false"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
        android:name=".MaliciousActivity"
        android:label="@string/title_activity_main"
        android:exported="true" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

MaliciousActivity.java

package org.jssec.android.intent.maliciousactivity;

import java.util.List;
import java.util.Set;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

public class MaliciousActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.malicious_activity);
        // Get am ActivityManager instance.
        ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
        // Get 100 recent task info.
        List<ActivityManager.RecentTaskInfo> list = activityManager
            .getRecentTasks(100, ActivityManager.RECENT_WITH_EXCLUDED);
        for (ActivityManager.RecentTaskInfo r : list) {
            // Get Intent sent to root Activity and Log it.
            Intent intent = r.baseIntent;
            Log.v("baseIntent", intent.toString());
            Log.v(" action:", intent.getAction());
            Log.v(" data:", intent.getDataString());
            if (r.origActivity != null) {
                Log.v(" pkg:", r.origActivity.getPackageName() + r.origActivity.getClassName());
            }
            Bundle extras = intent.getExtras();
            if (extras != null) {
                Set<String> keys = extras.keySet();
                for(String key : keys) {
                    Log.v(" extras:", key + "=" + extras.get(key).toString());
                }
            }
        }
    }
}

Вы можете использовать функцию getRecentTasks() класса ActivityManager, чтобы получить указанный элемент истории задач. Информация о каждой задаче хранится в экземпляре класса ActivityManager.RecentTaskInfo, но намерение, отправленное в корневую активность, сохраняется в его переменной-члене baseIntent. Поскольку корневая активность — это активность, которая была запущена при создании задачи, важно не удовлетворять следующим двум условиям при вызове активности:

  • новая задача создается при вызове активности;
  • вызываемая активность является корневой активностью и уже существует на переднем или заднем плане.

4.1.3.4 Корневая активность

Корневая активность — это точка старта для задачи. Другими словами, это активность, запущенная при создании задачи. Например, когда действие по умолчанию запускается средством запуска, это действие будет корневой активностью. Согласно спецификации Android, содержимое намерения, отправленного в корневую активность, может быть прочитано из любого приложения. Поэтому необходимо принять меры предосторожности, чтобы чувствительная информация не отправлялась в корневую активность. В этом руководстве были разработаны следующие три правила, чтобы избежать вызова активности в качестве корневой активности:

  • не указывать taskAffinity;
  • не указывать launchMode;
  • не устанавливать FLAG_ACTIVITY_NEW_TASK в намерении, отправленном в активность.

Рассмотрим случай, когда активность может стать одной из следующих корневых активностей. Вызываемая активность становится корневой активностью в зависимости от следующего содержания:

  • режим запуска вызываемой активности;
  • задача и режим запуска вызываемой активности.

Сначала я объясню «режим запуска вызываемой активности». Режим запуска активности можно установить, написав android:launchMode в AndroidManifest.xml. Если он не написан, он считается «стандартным». Кроме того, режим запуска также можно изменить, установив флаг в намерении. Флаг FLAG_ACTIVITY_NEW_TASK запускает активность в режиме singleTask.

Режимы запуска могут быть следующими. Я объясню их и связь с корневой активностью.

Стандартный (standard)

Активность, вызываемая в этом режиме, не является корневой, она принадлежит задаче вызывающей стороны. Каждый раз, когда она вызывается, генерируется экземпляр активности.

singleTop

Этот режим запуска аналогичен «стандартному», за исключением того, что новый экземпляр активности не создается, когда активная активность отображается на переднем плане задачи.

singleTask

В этом режиме запуска активность определяется задачей на основе значения Affinity. Когда задача, соответствующая значению Affinity активности, не существует в фоновом или переднем плане, новая задача генерируется вместе с экземпляром активности. Когда задача существует, они не создаются. В первом случае существующая активность становится корнем.

singleInstance

Аналогично singleTask, за исключением следующих моментов. Только корневая активность может принадлежать новой созданной задаче. Таким образом, экземпляр активности, запущенный в этом режиме запуска, всегда является корневой активностью. Теперь следует отметить, что даже если задача уже существует и имя совпадает с именем вызываемой активности Affinity, но класс вызываемой активности и класс активности, содержащийся в задаче, различаются.

Из вышесказанного мы знаем, что активность, запущенная в режимах singleTask или singleInstance, может стать корневой. Чтобы обеспечить безопасность приложения, его не следует запускать в этих режимах.

Далее я объясню «задачу вызываемой активности и ее режим запуска». Даже если активность вызывается в «стандартном» режиме, она все равно может стать корневой активностью. В некоторых случаях это зависит от состояния задачи активности.

Например, рассмотрим задачу вызываемой активности, которая уже выполняется в фоновом режиме. Здесь проблема заключается в том, что экземпляр активности задачи был запущен в режиме singleInstance. Когда активность с тем же значением Affinity вызывается «стандартно», создание новой задачи ограничено существующей активностью singleInstance. Однако, если имена классов каждой активности совпадают, задача не создается и используется существующий экземпляр активности. В любом случае вызываемая активность станет корневой активностью.

Как описано выше, условия вызова корневой активности сложны и зависят от состояния выполнения. Поэтому при разработке приложения лучше всего вызывать активность «стандартно».

Это пример, в котором намерение отправляется частной активности, которое может быть прочитано из других приложений. Пример кода показывает, что частная активность вызывающего действия запускается в режиме singleInstance. В этом примере кода, частная активность запускается в «стандартном» режиме, но из-за условия singleInstance вызывающей стороны Activity, эта частная активность становится корнем новой задачи Activity.

В этом случае, чувствительная информация, отправленная частной активности, записывается в историю задач, поэтому её можно прочитать из других приложений.

Для справки, вызывающая сторона и частная активности имеют одинаковую Affinity.

AndroidManifest.xml (не рекомендуется)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.activity.singleinstanceactivity">

    <application
        android:allowBackup="false"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">

        <!-- Устанавливает режим запуска корневой активности на "singleInstance". -->
        <!-- Не используйте taskAffinity -->
        <activity
            android:name="org.jssec.android.activity.singleinstanceactivity.PrivateUserActivity"
            android:label="@string/app_name"
            android:launchMode="singleInstance"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-фильтр>
        </activity>

        <!-- Частная активность -->
        <!-- Устанавливает режим запуска на "standard". -->
        <!-- Не использовать taskAffinity -->
        <активность
        android:name="org.jssec.android.activity.singleinstanceactivity.PrivateActivity"
        android:label="@string/app_name"
        android:exported="false"/>
    </application>
</manifest>

Частная активность просто возвращает полученный интент.

PrivateActivity.java

package org.jssec.android.activity.singleinstanceactivity;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class PrivateActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.private_activity);
        // Обрабатываем интенты безопасно, даже если они отправлены из того же приложения.
        // Опущено, так как это пример. Пожалуйста, обратитесь к разделу «3.2. Обработка входных данных тщательно и безопасно».
        String param = getIntent().getStringExtra("PARAM");
        Toast.makeText(this, String.format("Received param: ¥"%s¥"", param), Toast.LENGTH_LONG).show();
    }

    public void onReturnResultClick(View view) {
        Intent intent = new Intent();
        intent.putExtra("RESULT", "Sensitive Info");
        setResult(RESULT_OK, intent);
        finish();
    }
}

На стороне вызывающего, частная активность запускается в «стандартном» режиме, и интенты не имеют никаких флагов.

PrivateUserActivity.java

package org.jssec.android.activity.singleinstanceactivity;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class PrivateUserActivity extends Activity {

    private static final int REQUEST_CODE = 1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.user_activity);
    }

    public void onUseActivityClick(View view) {
        // Запускаем частную активность с «стандартным» режимом запуска.
        Intent intent = new Intent(this, PrivateActivity.class);
        intent.putExtra("PARAM", "Sensitive Info");
        startActivityForResult(intent, REQUEST_CODE);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode != RESULT_OK) return;
        switch (requestCode) {
            case REQUEST_CODE:
                String result = data.getStringExtra("RESULT");
                // Обрабатываем полученные данные результата тщательно и безопасно,
                // даже если данные поступили от активности в том же приложении.
                // Опущено, поскольку это пример. Пожалуйста, обратитесь к разделу «3.2 Обработка входных данных тщательно и безопасно».
                Toast.makeText(this, Str();
                break;
        }
    }
}

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-android-app-sec-guidebook.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-android-app-sec-guidebook.git
oschina-mirror
wizardforcel-android-app-sec-guidebook
wizardforcel-android-app-sec-guidebook
master