安卓應用安全指南六、困難問題

apachecn_飛龍發表於2018-04-05

六、困難問題

原書:Android Application Secure Design/Secure Coding Guidebook

譯者:飛龍

協議:CC BY-NC-SA 4.0

在 Android 中,由於 Android 作業系統規範或 Android 作業系統提供的功能,難以確保應用實現的安全性。 這些功能被惡意第三方濫用或使用者不小心使用,始終存在可能導致資訊洩露等安全問題的風險。 本章通過指出開發人員可以針對這些功能採取的風險緩解計劃,將一些需要引起注意的主題挑選為文章。

6.1 來自剪貼簿的資訊洩露風險

複製和貼上是使用者經常以不經意的方式使用的功能。 例如,不少使用者使用這些功能來儲存好奇或重要的資訊,將郵件或網頁中的東西記到記事本中,或者從儲存密碼的記事本複製並貼上密碼,以便不會提前忘記。 這些明顯非常隨意的行為,但實際上存在使用者處理的資訊可能被盜的隱藏風險。

這個風險與 Android 系統中的複製貼上機制有關。 使用者或應用複製的資訊,曾經儲存在稱為剪貼簿的緩衝區中。 儲存在剪貼簿中的資訊,在被使用者或應用貼上時,分發給其他應用。 所以這個剪貼簿功能中存在導致資訊洩漏的風險。 這是因為剪貼簿的實體在系統中是唯一的,並且任何應用都可以使用ClipboardManager,隨時獲取儲存在剪貼簿中的資訊。 這意味著使用者複製/剪下的所有資訊都會洩露給惡意應用。

因此,考慮到 Android 作業系統的規範,應用開發人員需要採取措施,儘量減少資訊洩露的可能性。

6.1.1 示例程式碼

粗略地說,有兩種對策用於減輕來自剪貼簿的資訊洩露風險

  1. 從其他應用複製到你的應用時採取對策。
  2. 從你的應用複製到其他應用時採取對策。

首先,讓我們討論上面的對策(1)。 假設使用者從其他應用(如記事本,Web 瀏覽器或郵件應用)複製字串,然後將其貼上到你的應用的EditText中。 事實證明,在這種情況下,基本沒有對策,來防止由於複製和貼上而導致的敏感資訊洩漏。 由於 Android 中沒有功能來控制第三方應用的複製操作。 因此,就對策(1)而言,除了向使用者解釋複製和貼上敏感資訊的風險外,沒有任何方法,只能繼續讓使用者自行減少操作。

接下來的討論是上面的對策(2),假設使用者複製應用中顯示的敏感資訊。 在這種情況下,防止洩漏的有效對策是,禁止來自檢視(TextViewEditText等)的複製/剪下操作。 如果輸入/輸出敏感資訊(如個人資訊)的檢視中,沒有複製/剪下功能,資訊洩漏永遠不會通過剪貼簿在你的應用發生。

有幾種禁止複製/剪下的方法。 本節介紹簡單有效的方法:一種方法是禁用檢視的長按,另一種方法是在選擇字串時從選單中刪除複製/剪下條目。

對策的必要性可以根據圖 6.1-1 的流程確定。 在圖 6.1-1 中,“輸入型別固定為密碼屬性”表示,輸入型別在應用執行時必須是以下三種之一。 在這種情況下,由於預設禁止複製/剪下,因此不需要採取對策。

  • InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD
  • InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD
  • InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD

以下小節使用每個示例程式碼詳細介紹了對策。

6.1.1.1 選擇字串時,從選單中刪除複製/剪下條目

在 Android 3.0(API Level 11)之前不能使用TextView.setCustomSelectionActionMODECallback()方法。 在這種情況下,禁止複製/剪下的最簡單方法是禁用檢視的長按。 禁用檢視的長按可以在layout.xml檔案中規定。

下面展示了示例程式碼,用於從EditText中的字串選擇選單中刪除複製/剪下條目。

要點:

  1. 從字串選擇選單中刪除android.R.id.copy
  2. 從字串選擇選單中刪除android.R.id.cut

UncopyableActivity.java

package org.jssec.android.clipboard.leakage;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;

public class UncopyableActivity extends Activity {

    private EditText copyableEdit;
    private EditText uncopyableEdit;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.uncopyable);
        copyableEdit = (EditText) findViewById(R.id.copyable_edit);
        uncopyableEdit = (EditText) findViewById(R.id.uncopyable_edit);
        // By setCustomSelectionActionMODECallback method,
        // Possible to customize menu of character string selection.
        uncopyableEdit.setCustomSelectionActionModeCallback(actionModeCallback);
    }

    private ActionMode.Callback actionModeCallback = new ActionMode.Callback() {

        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false;
        }

        public void onDestroyActionMode(ActionMode mode) {
        }

        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            // *** POINT 1 *** Delete android.R.id.copy from the menu of character string selection.
            MenuItem itemCopy = menu.findItem(android.R.id.copy);
            if (itemCopy != null) {
                menu.removeItem(android.R.id.copy);
            }
            // *** POINT 2 *** Delete android.R.id.cut from the menu of character string selection.
            MenuItem itemCut = menu.findItem(android.R.id.cut);
            if (itemCut != null) {
                menu.removeItem(android.R.id.cut);
            }
            return true;
        }

        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            return false;
        }
    };

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.uncopyable, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

6.1.1.2 禁用檢視的長按

禁止複製/剪下也可以通過禁用檢視的長按來實現。 禁用檢視的長按可以在layout.xml檔案中規定。

要點:

  1. 在檢視中將android:longClickable設定為false,來禁止複製/剪下。

unlongclickable.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/unlongclickable_description" />
    <!-- EditText to prohibit copy/cut EditText -->
    <!-- *** POINT 1 *** Set false to android:longClickable in View to prohibit copy/cut. -->
    <EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:longClickable="false"
    android:hint="@string/unlongclickable_hint" />
</LinearLayout>

6.1.2 規則書

將敏感資訊從你的應用複製到其他應用時,請遵循以下規則:

6.1.2.1 禁用檢視中顯示的複製/剪下字串(必需)

如果應用中存在顯示敏感資訊的檢視,並且允許在檢視中像EditText一樣複製/剪下資訊,資訊可能會通過剪貼簿洩漏。 因此,必須在顯示敏感資訊的檢視中禁用複製/剪下。 有兩種方法禁用複製/剪下。 一種方法是從字串選擇選單中刪除複製/剪下條目,另一種方法是禁用檢視的長按。 請參閱“6.1.3.1 應用規則時的注意事項”。

6.1.3 高階話題

6.1.3.1 應用規則時的注意事項

TextView中,選擇字串是不可能的,因此通常不需要對策,但在某些情況下,可以複製取決於應用的規範。選擇/複製字串的可能性可以通過使用TextView.setTextIsSelectable()方法動態決定。將TextView設定為可以複製時,應調查在TextView中顯示任何敏感資訊的可能性,並且如果有任何可能性,則不應將其設定為可複製的。

另外,在“6.1.1 示例程式碼”的決策流程中描述,根據EditText的輸入型別(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD等),假設輸入型別是密碼,通常不需要任何對策,因為複製字串是預設禁止的。但是,如“5.1.2.2 提供以明文顯示密碼的選項(必需)”中所述,如果準備了【以明文顯示密碼】的選項,則在以明文顯示密碼的情況下,輸入型別將會改變,並且啟用複製/剪下。因此應該要求採取同樣的對策。

請注意,開發者在應用規則時,還應考慮到應用的可用性。 例如,在使用者可以自由輸入文字的檢視的情況下,如果因輸入敏感資訊的可能性很小而禁用了複製/剪下,使用者可能會感到不便。 當然,該規則應該無條件地,應用於處理非常重要的資訊或獨立的敏感資訊的檢視,但在檢視之外的情況下,以下問題將幫助開發人員瞭解如何正確處理檢視。

  • 準備一些專門用於敏感資訊的其他元件
  • 當嚮應用的貼上是顯而易見的時候,用其他方法傳送資訊
  • 提醒使用者注意輸入/輸出資訊
  • 重新審視檢視的必要性

資訊洩露風險的根源在於,Android 作業系統中剪貼簿和剪貼簿管理器的規範不考慮安全風險。 應用開發人員需要在使用者完整性,可用性,功能等方面建立更高質量的應用。

6.1.3.2 儲存在剪貼簿中的操作資訊

正如“6.1 來自剪貼簿的資訊洩漏風險”中所述,應用可以使用ClipboardManager,操作儲存在剪貼簿中的資訊。另外,不需要為使用ClipboardManager設定特定的許可權,因此應用可以在不被使用者識別的情況下,使用ClipboardManager

儲存在剪貼簿中的資訊稱為ClipData,可以通過ClipboardManager.getPrimaryClip()方法獲得。如果通過ClipboardManager.addPrimaryClipChangedListener()方法,將偵聽器註冊到ClipboardManager,並實現了OnPrimaryClipChangedListener,則每次使用者執行復制/剪下操作時都會呼叫監聽器。因此可以在不忽略時間的情況下獲得ClipData。在任何應用中執行復制/剪下操作時,都會呼叫監聽器。

下面顯示了服務的原始碼,無論什麼時候在裝置中執行復制/剪下,它都會獲取ClipData並通過Toast顯示。你可以意識到,儲存在剪貼簿中的資訊被洩露出來,就是由於下面的簡單程式碼。有必要注意,敏感資訊至少不會由以下原始碼使用。

ClipboardListeningService.java

package org.jssec.android.clipboard;

import android.app.Service;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ClipboardManager.OnPrimaryClipChangedListener;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class ClipboardListeningService extends Service {

    private static final String TAG = "ClipboardListeningService";
    private ClipboardManager mClipboardManager;

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mClipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
        if (mClipboardManager != null) {
            mClipboardManager.addPrimaryClipChangedListener(clipListener);
        } else {
            Log.e(TAG, "Failed to get ClipboardService . Service is closed.");
            this.stopSelf();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mClipboardManager != null) {
            mClipboardManager.removePrimaryClipChangedListener(clipListener);
        }
    }

    private OnPrimaryClipChangedListener clipListener = new OnPrimaryClipChangedListener() {

        public void onPrimaryClipChanged() {
            if (mClipboardManager != null && mClipboardManager.hasPrimaryClip()) {
                ClipData data = mClipboardManager.getPrimaryClip();
                ClipData.Item item = data.getItemAt(0);
                Toast.makeText(
                    getApplicationContext(),
                    "Character stirng that is copied or cut:¥n"
                        + item.coerceToText(getApplicationContext()),
                    Toast.LENGTH_SHORT)
                    .show();
            }
        }
    };
}

接下來,下面顯示了Activity的示例程式碼,它使用上面涉及的ClipboardListeningService

ClipboardListeningActivity.java

package org.jssec.android.clipboard;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class ClipboardListeningActivity extends Activity {

    private static final String TAG = "ClipboardListeningActivity";

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

    public void onClickStartService(View view) {
        if (view.getId() != R.id.start_service_button) {
            Log.w(TAG, "View ID is incorrect.");
        } else {
            ComponentName cn = startService(
                new Intent(ClipboardListeningActivity.this, ClipboardListeningService.class));
            if (cn == null) {
                Log.e(TAG, "Failed to launch the service.");
            }
        }
    }
    public void onClickStopService(View view) {
        if (view.getId() != R.id.stop_service_button) {
            Log.w(TAG, "View ID is incorrect.");
        } else {
            stopService(new Intent(ClipboardListeningActivity.this, ClipboardListeningService.class));
        }
    }
}

到目前為止,我們已經介紹了獲取儲存在剪貼簿上的資料的方法。 也可以使用ClipboardManager.setPrimaryClip()方法在剪貼簿上儲存新資料。

請注意,setPrimaryClip()方法將覆蓋儲存在剪貼簿中的資訊,因此使用者的複製/剪下儲存的資訊可能會丟失。 當使用這些方法提供自定義複製/剪下功能時,必須按需設計/實現,以防止儲存在剪貼簿中的內容改變為意外內容,通過顯示對話方塊來通知內容將被改變。


相關文章