Android應用安全防禦的點點滴滴

前言

facebook數據泄露,國內某公司信息泄露,國內某酒店開房記錄泄露...,近年來,信息安全愈來愈讓人堪憂,做爲移動開發人員,也是憂心忡忡,在經理的指示下,開始Android信息安全防禦的旅程javascript

一. webView

在如今安卓應用原生開發中,爲了追求開發的效率以及移植的便利性,使用WebView做爲業務內容展現與交互的主要載體是個不錯的折中方案。html

那麼在這種Hybrid(混合式) App中,不免就會遇到頁面JS須要與Java相互調用,調用Java方法去作那部分網頁JS不能完成的功能。網上的方法能夠告訴咱們這個時候咱們可使用addjavascriptInterface來注入原生接口到JS中,可是在安卓4.2如下的系統中,這種方案卻咱們的應用帶來了很大的安全風險。攻擊者若是在頁面執行一些非法的JS(誘導用戶打開一些釣魚網站以進入風險頁面),極有可能反彈拿到用戶手機的shell權限。接下來攻擊者就能夠在後臺默默安裝木馬,徹底洞穿用戶的手機java

==那麼如何避免呢?咱們從如下幾個方面進行優化webView.==android

1. 謹慎支持JS功能,避免沒必要要的麻煩

提到對於Android4.2如下的JS任意代碼執行漏洞 , 還有包括證書版本太低不安全的因素 . 對於低版本 , 對於2019年將來來講,建議能夠放棄支持.web

2. 請使用https的連接

  • 第一是安全;
  • 第二是避免被噁心的運營商劫持,插入廣告,影響用戶體驗

我以爲這個頗有必要,不只僅是由於安全,包括微信公衆號,googleplay 都在強制要求開發者必須使用https算法

3. 處理file協議安全漏洞

//若不需支持,則直接禁止 file 協議
setAllowFileAccess(false);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
複製代碼

4. 密碼明文保存漏洞

因爲webView默認開啓密碼保存功能,因此在用戶輸入密碼時,會彈出提示框,詢問用戶是否保存。若選擇保存,則密碼會以明文形式保存到 ==/data/data/com.package.name/databases/webview.db==中,這樣就有被盜取密碼的危險。因此咱們應該禁止網頁保存密碼,設置shell

WebSettings.setSavePassword(false)
複製代碼

5. 開啓安全瀏覽模式

<manifest> 
		<meta-data
    android:name="android.webkit.WebView.EnableSafeBrowsing"
    android:value="true" />
  		<application> ... </application>
 </manifest>

複製代碼

啓用安全瀏覽模式後,WebView 將參考安全瀏覽的惡意軟件和釣魚網站數據庫檢查訪問的 URL ,在用戶打開以前給予危險提示,體驗相似於Chrome瀏覽器 . 若是遇到不安全網站,會有如圖所示狀況 數據庫

二. 通訊安全

這個也是重中之重,大多數的數據安全都是經過網絡攻擊形成的,那麼咱們如何去避免呢?編程

1. 使用HTTPS協議

HTTPS的主要思想是在不安全的網絡上建立一安全信道,並可在使用適當的加密包和服務器證書可被驗證且可被信任時,對竊聽和中間人攻擊提供合理的防禦。能夠說是很是基礎的安全防禦級別了。後端

2. 驗證證書.

android中實現Https基本就這兩種方式,一種是不驗證證書,一種是有驗證證書(預防釣魚)。

第二種驗證證書稍微複雜一點,這種方式也只能簡單的防止釣魚,不能有效的防止釣魚。防止釣魚最終仍是靠用戶分辨,在正規渠道下載應用。

==可是若是把證書放在apk中也是一件很危險的事情,由於如今的反編譯技術不得不服,因此目前以爲最好的方式,就是放在.so文件中.==能夠進一步的下降風險 .

3. 通訊數據儘可能不使用明文形式.

先後端進行自定義算法進行加密,md5 base64 AES RSA 等等,==算法推薦.so和java相互調用的形式==.

4. 防抓包

System.getProperty("http.proxyHost");  
System.getProperty("http.proxyPort");  

複製代碼

正常這兩行代碼獲取的是null,若是返回不爲空,就是掛代理了,那麼就能夠考慮是否不給數據了

5. token等等進一步的防禦措施.

道高一尺,魔高一丈,將來的路還會很長.

三. 數據存儲安全

有了數據就得存放,若是存放,就會被別人發現.因此得存好嘍

1. 隱藏數據存儲位置

在Andoid設備中,以'.'開頭文件或者文件夾是不可見的,而且也能夠進行讀寫操做.若是隱藏了存儲了文件位置,就能夠避免用戶誤操做和被發現的機會.

2. 存儲內容不要使用明文

就算被找到,內容若是以密文的形式,也會下降被泄漏的風險

3. 代碼中禁止硬編碼重要信息內容

硬編碼很容易被反編譯找到.建議存儲到.so中(雖然.so也能夠被破解,可是相較於java更安全點)

4. 存儲位置推薦

儘可能存儲到手機內部存儲上,不要存儲到外部存儲卡上(由於手機內部存儲只對相應的應用開放,外部存儲對全部的應用都開放)

四. 組件安全

規範安卓標準組件(Activity、Service、Receiver、Provider)的訪問權限

1. 設置權限開放屬性:android:exported=["true" | "false"]

exported屬性爲四大組件共有屬性,其中含義大同小異。默認值由其包含 ==== 與否決定。若未包含====,默認爲「false」,若存在至少一個====,則默認值爲「true」。

  • 在Activity中:

表示是否容許外部應用組件啓動。若爲「false」,則 Activity 只能由同一應用或同一用戶 ID 的不一樣應用啓動。

  • 在Service中:

表示是否容許外部應用組件調用服務或與其進行交互。若爲「false」,則 Activity 只能由同一應用或同一用戶 ID 的不一樣應用啓動。

  • 在Receiver中:

表示是否能夠接收來自其應用程序以外的消息,如來自系統或或其餘應用的廣播。若爲「 false」,則廣播接收器只能接收具備相同用戶ID的相同應用程序或應用程序的組件發送的消息。

  • 在Provider中:

表示是否容許其餘應用程序訪問內容提供器。若爲「false」,則具備與Provider相同的用戶ID(UID)的應用程序才能訪問它。若是須要給其餘應用程序提供內容,則應當限定讀寫權限。

2. 配置自定義權限

自定義權限,限制本身的組件訪問,詳情看Android自定義權限與使用

3. 使用更加安全高效的LocalBroadcastManager

區別基於Binder實現的BroadcastReceiver,LocalBroadcastManager 是基於Handler實現的,擁有更高的效率與安全性。安全性主要體如今數據僅限於應用內部傳輸,避免廣播被攔截、僞造、篡改的風險。簡單瞭解下用法:

(1).自定義BroadcastReceiver

public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //Do SomeThing Here
    }
}
複製代碼

(2).註冊Receive

MyReceiver myReceiver = new MyReceiver();
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this);
IntentFilter filter = new IntentFilter();
filter.addAction("MY_ACTION");
localBroadcastManager.registerReceiver(myReceiver, filter);
複製代碼

(3).發送本地廣播

Bundle bundle = new Bundle();
bundle.putParcelable("DATA", content);
Intent intent = new Intent();
intent.setAction("MY_ACTION");
intent.putExtras(bundle);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
複製代碼

(4).在Activity銷燬時取消註冊

@Override
protected void onDestroy() {
    super.onDestroy();
    localBroadcastManager.unregisterReceiver(myReceiver);
}
複製代碼

4. Application相關屬性配置

(1). debugable屬性 android:debuggable=["true" | "false"]

不少人說要在發佈的時候手動設置該值爲false,其實根據官方文檔說明,默認值就是false。

(2). allowBackup屬性 android:allowBackup=["true" | "false"]

設置是否支持備份,默認值爲true,應當慎重支持該屬性,避免應用內數據經過備份形成的泄漏問題。

5. 自定義鍵盤.

對於操做密碼等危險輸入的狀況,能夠考慮自定義鍵盤處理,防止經過鍵盤被盜用密碼.

五. 其餘防禦措施

多作一層安全措施,少一點風險.

1. 代碼安全

  • 加固 360加固寶 等等
  • 混淆

2. 控制日誌輸出

自定義工具類,不要在線上出現敏感的信息

3. 漏洞檢測工具

各類雲測平臺進行測試

  • testin 雲測
  • 阿里雲測 免費的
  • 騰訊的 wetest

4. 防止模擬器

判斷手機是否包含藍牙等模塊,一些信息是否跟手機真機不一致等.防止經過模擬器篡改信息

5. 二次打包

經過判斷簽名信息,防止,被二次打包,調試應用信息

6. 帳號與設備綁定

帳號與相應設備進行綁定,若是發現與經常使用設備不符合,增長短信登陸形式進行從新登陸

7. dex文件的校驗

重編譯apk其實就是重編譯了classes.dex文件,重編譯後,生成的classes.dex文件的hash值就改變了,所以咱們能夠經過檢測安裝後classes.dex文件的hash值來判斷apk是否被重打包過。

(1). 讀取應用安裝目錄下/data/app/xxx.apk中的classes.dex文件並計算其哈希值,將該值與軟件發佈時的classes.dex哈希值作比較來判斷客戶端是否被篡改。

(2). 讀取應用安裝目錄下/data/app/xxx.apk中的META-INF目錄下的MANIFEST.MF文件,該文件詳細記錄了apk包中全部文件的哈希值,所以能夠讀取該文件獲取到classes.dex文件對應的哈希值,將該值與軟件發佈時的classes.dex哈希值作比較就能夠判斷客戶端是否被篡改。

爲了防止被破解,軟件發佈時的classes.dex哈希值應該存放在服務器端。

8.調試器檢測

爲了防止apk被動態調試,能夠檢測是否有調試器鏈接。在Application類中提供了isDebuggerConnected()方法用於檢測是否有調試器鏈接,若是發現有調試器鏈接,能夠直接退出程序。

9.是否root

檢測是否包含su程序,和ro.secure是否爲1,若是root了,能夠禁止某些核心功能 檢測是否root的代碼

public boolean isRoot() {
        int secureProp = getroSecureProp();
        if (secureProp == 0)//eng/userdebug版本,自帶root權限
            return true;
        else return isSUExist();//user版本,繼續查su文件
    }

    private int getroSecureProp() {
        int secureProp;
        String roSecureObj = CommandUtil.getSingleInstance().getProperty("ro.secure");
        if (roSecureObj == null) secureProp = 1;
        else {
            if ("0".equals(roSecureObj)) secureProp = 0;
            else secureProp = 1;
        }
        return secureProp;
    }

    private boolean isSUExist() {
        File file = null;
        String[] paths = {"/sbin/su",
                "/system/bin/su",
                "/system/xbin/su",
                "/data/local/xbin/su",
                "/data/local/bin/su",
                "/system/sd/xbin/su",
                "/system/bin/failsafe/su",
                "/data/local/su"};
        for (String path : paths) {
            file = new File(path);
            if (file.exists()) return true;//能夠繼續作可執行判斷
        }
        return false;
    }

複製代碼

10. 是否裝有xposd框架

檢測是否安裝有xposd框架,若是有提示並隱藏核心功能模塊.接口禁用某些功能 全部的方案迴歸到一點:==判斷xposed的包是否存在。== (1).是經過主動拋出異常查棧信息; (2).是主動反射調用。

private static final String XPOSED_HELPERS = "de.robv.android.xposed.XposedHelpers";
    private static final String XPOSED_BRIDGE = "de.robv.android.xposed.XposedBridge";

    //手動拋出異常,檢查堆棧信息是否有xp框架包
    public boolean isEposedExistByThrow() {
        try {
            throw new Exception("gg");
        } catch (Exception e) {
            for (StackTraceElement stackTraceElement : e.getStackTrace()) {
                if (stackTraceElement.getClassName().contains(XPOSED_BRIDGE)) return true;
            }
            return false;
        }
    }

    //檢查xposed包是否存在
    public boolean isXposedExists() {
        try {
            Object xpHelperObj = ClassLoader
                    .getSystemClassLoader()
                    .loadClass(XPOSED_HELPERS)
                    .newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
            return true;
        } catch (IllegalAccessException e) {
            //實測debug跑到這裏報異常
            e.printStackTrace();
            return true;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return false;
        }

        try {
            Object xpBridgeObj = ClassLoader
                    .getSystemClassLoader()
                    .loadClass(XPOSED_BRIDGE)
                    .newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
            return true;
        } catch (IllegalAccessException e) {
            //實測debug跑到這裏報異常
            e.printStackTrace();
            return true;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    //嘗試關閉xp的全局開關,親測可用
    public boolean tryShutdownXposed() {
        if (isEposedExistByThrow()) {
            Field xpdisabledHooks = null;
            try {
                xpdisabledHooks = ClassLoader.getSystemClassLoader()
                        .loadClass(XPOSED_BRIDGE)
                        .getDeclaredField("disableHooks");
                xpdisabledHooks.setAccessible(true);
                xpdisabledHooks.set(null, Boolean.TRUE);
                return true;
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
                return false;
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                return false;
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                return false;
            }
        } else return true;
    }

複製代碼

參考資料

相關文章
相關標籤/搜索