安卓應用安全指南4.5.2使用SQLite規則書

apachecn_飛龍發表於2018-03-22

安卓應用安全指南 4.5.2 使用 SQLite 規則書

原書:Android Application Secure Design/Secure Coding Guidebook

譯者:飛龍

協議:CC BY-NC-SA 4.0

使用 SQLite 時,遵循以下規則:

4.5.2.1 正確設定 DB 檔案位置和訪問許可權(必需)

考慮到 DB 檔案資料的保護,DB 檔案位置和訪問許可權設定是需要一起考慮的非常重要的因素。 例如,即使正確設定了檔案訪問權,如果 DB 檔案位於無法設定訪問權的位置,則任何人可以訪問 DB 檔案,例如, SD 卡。 如果它位於應用目錄中,如果訪問許可權設定不正確,它最終將允許意外訪問。 以下是正確分配和訪問許可權設定的一些要點,以及實現它們的方法。 為了保護資料庫檔案(資料),對於位置和訪問許可權設定,需要執行以下兩點。

1) 位置

位於可以由Context#getDatabasePath(String name)獲取的檔案路徑,或者在某些情況下,可以由Context#getFilesDir11獲取的目錄。

2) 訪問許可權

設定為MODE_PRIVATE(只能由建立檔案的應用訪問)模式。

通過執行以下2點,即可
建立其他應用無法訪問的 DB 檔案。 以下是執行它們的一些方法。

  1. 使用SQLiteOpenHelper
  2. 使用Context#openOrCreateDatabase

建立 DB 檔案時,可以使用SQLiteDatabase#openOrCreateDatabase。 但是,使用此方法時,可以在某些 Android 智慧手機裝置中建立可從其他應用讀取的 DB 檔案。 所以建議避免這種方法,並使用其他方法。 上述量種方法的每個特徵如下 [11]

[11] 這兩種方法都提供了(包)目錄下的路徑,只能由指定的應用讀取和寫入。

使用SQLiteOpenHelper

當使用SQLiteOpenHelper時,開發人員不需要擔心很多事情。 建立一個從SQLiteOpenHelper派生的類,併為構造器的引數指定DB名稱(用於檔名)[12],然後滿足上述安全要求的 DB 檔案會自動建立。

[12] (未在 Android 參考中記錄)由於可以在SQLiteOpenHelper實現中,將完整檔案路徑指定為資料庫名稱,因此需要注意無意中指定不能控制訪問許可權的地方(路徑)(例如 SD 卡)。

對於如何使用,請參閱“4.5.1.1 建立/運算元據庫”的具體使用方法。

使用Context#openOrCreateDatabase

使用Context#openOrCreateDatabase方法建立資料庫時,檔案訪問權應由選項指定,在這種情況下,請明確指定MODE_PRIVATE

對於檔案安排,資料庫名稱(用於檔名)可以像SQLiteOpenHelper一樣指定,檔案將在滿足上述安全要求的檔案路徑中自動建立。 但是,也可以指定完整路徑,因此有必要注意指定 SD 卡時,即使指定MODE_PRIVATE,其他應用也可以訪問。

MainActivity.java(顯式設定 DB 訪問權的示例)

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    //Construct database
    try {
        //Create DB by setting MODE_PRIVATE
        db = Context.openOrCreateDatabase("Sample.db", MODE_PRIVATE, null);
    } catch (SQLException e) {
        //In case failed to construct DB, log output
        Log.e(this.getClass().toString(), getString(R.string.DATABASE_OPEN_ERROR_MESSAGE));
        return;
    }
    //Omit other initial process
}

訪問許可權有三種可能的設定:MODE_PRIVATEMODE_WORLD_READABLEMODE_WORLD_WRITEABLE。 這些常量可以由或運算子一起指定。 但是,除API_PRIVATE之外的所有設定,都將在 API 級別 17 和更高版本中被棄用,並且會在 API 級別 24 和更高版本中導致安全異常。 即使對於 API 級別 15 及更早版本的應用,通常最好不要使用這些標誌 [13]。

[13] MODE_WORLD_READABLEMODE_WORLD_WRITEABLE的更多資訊,以及其使用的注意事項,請參見“4.6.3.2 訪問目錄的許可權設定”。

  • MODE_PRIVATE只有建立者應用可以讀寫
  • MODE_WORLD_READABLE建立者應用可以讀寫,其他人只能讀
  • MODE_WORLD_WRITEABLE建立者應用可以讀寫,其他人只能寫

4.5.2.2 與其它應用共享 DB 資料時,將內容供應器用於訪問控制(必需)

與其他應用共享 DB 資料的方法是,將 DB 檔案建立為WORLD_READABLEWORLD_WRITEABLE,以便其他應用直接訪問。 但是,此方法不能限制訪問或運算元據庫的應用,因此資料可以由非預期的一方(應用)讀或寫。 因此,可以認為資料的機密性或一致性方面可能會出現一些問題,或者可能成為惡意軟體的攻擊目標。

如上所述,在 Android 中與其他應用共享資料庫資料時,強烈建議使用內容供應器。 內容供應器存在一些優點,不僅從安全的角度來實現對 DB 的訪問控制,而且從設計角度來看, DB 綱要結構可以隱藏到內容中。

4.5.2.3 在 DB 操作期間處理變數引數時,必需使用佔位符(必需)

在防止 SQL 注入的意義上,將任意輸入值併入 SQL 語句時,應使用佔位符。 下面有兩個方法用佔位符執行 SQL。

  1. 使用SQLiteDatabase#compileStatement(),獲取SQLiteStatement,然後使用SQLiteStatement#bindString()bindLong()等,將引數放置到佔位符之後。
  2. SQLiteDatabese類上呼叫execSQL()insert()update()delete()query()rawQuery()replace()時,使用具有佔位符的 SQL 語句。

另外,通過使用SQLiteDatabase#compileStatement()執行SELECT命令時,存在“僅獲取第一個元素作為SELECT命令的結果”的限制,所以用法是有限的。

在任何一種方法中,提供給佔位符的資料內容最好根據應用要求事先檢查。 以下是每種方法的進一步解釋。

使用SQLiteDatabase#compileStatement()

資料以下列步驟提供給佔位符:

  1. 使用SQLiteDatabase#compileStatement()獲取包含佔位符的 SQL 語句,如SQLiteStatement
  2. 使用bindLong()bindString()方法為建立的SQLiteStatement物件設定佔位符。
  3. 通過ExecSQLiteStatement物件的execute()方法執行 SQL。

DataInsertTask.java(佔位符的用例):

//Adding data task
public class DataInsertTask extends AsyncTask<String, Void, Void> {

    private MainActivity mActivity;
    private SQLiteDatabase mSampleDB;

    public DataInsertTask(SQLiteDatabase db, MainActivity activity) {
        mSampleDB = db;
        mActivity = activity;
    }

    @Override
    protected Void doInBackground(String... params) {
        String idno = params[0];
        String name = params[1];
        String info = params[2];
        //*** POINT 3 *** Validate the input value according the application requirements.
        if (!DataValidator.validateData(idno, name, info)) {
            return null;
        }
        // Adding data task
        //*** POINT 2 *** Use place holder
        String commandString = "INSERT INTO " + CommonData.TABLE_NAME + " (idno, name, info) VALUES (?, ?, ?)";
        SQLiteStatement sqlStmt = mSampleDB.compileStatement(commandString);
        sqlStmt.bindString(1, idno);
        sqlStmt.bindString(2, name);
        sqlStmt.bindString(3, info);
        try {
            sqlStmt.executeInsert();
        } catch (SQLException e) {
            Log.e(DataInsertTask.class.toString(), mActivity.getString(R.string.UPDATING_ERROR_MESSAGE));
        } finally {
            sqlStmt.close();
        }
        return null;
    }

    [...]
}

這是一種型別,它預先建立作為物件執行的 SQL 語句,並將引數分配給它。 執行的過程是固定的,所以沒有發生 SQL 注入的可能。 另外,通過重用SQLiteStatement物件可以提高流程效率。

使用SQLiteDatabase提供的每個方法:

SQLiteDatabase提供了兩種型別的資料庫操作方法。 一種是使用 SQL 語句,另一種是不使用 SQL 語句。 使用 SQL 語句的方法是SQLiteDatabase#execSQL()/rawQuery(),它以以下步驟執行。

1) 準備包含佔位符的 SQL 語句。

2) 建立要分配給佔位符的資料。

3) 傳遞 SQL 語句和資料作為引數,併為每個過程執行一個方法。

另一方面,SQLiteDatabase#insert()/update()/delete()/query()/replace()是不使用 SQL 語句的方法。當使用它們時,資料應該按照以下步驟來準備。

1) 如果有資料要插入/更新到資料庫,請註冊到ContentValues

2) 傳遞ContentValues作為引數,併為每個過程執行一個方法(例如,SQLiteDatabase#insert()

SQLiteDatabase#insert()(每個過程的方法的用例):

private SQLiteDatabase mSampleDB;
private void addUserData(String idno, String name, String info) {
    //Validity check of the value(Type, range), escape process
    if (!validateInsertData(idno, name, info)) {
        //If failed to pass the validation, log output
        Log.e(this.getClass().toString(), getString(R.string.VALIDATION_ERROR_MESSAGE));
        return;
    }
    //Prepare data to insert
    ContentValues insertValues = new ContentValues();
    insertValues.put("idno", idno);
    insertValues.put("name", name);
    insertValues.put("info", info);
    //Execute Inser
    try {
        mSampleDb.insert("SampleTable", null, insertValues);
    } catch (SQLException e) {
        Log.e(this.getClass().toString(), getString(R.string.DB_INSERT_ERROR_MESSAGE));
        return;
    }
}

在這個例子中,SQL 命令不是直接寫入,而是使用SQLiteDatabase提供的插入方法。 SQL 命令沒有直接使用,所以在這種方法中也沒有 SQL 注入的可能。


相關文章