facebook數據泄露,國內某公司信息泄露,國內某酒店開房記錄泄露...,近年來,信息安全愈來愈讓人堪憂,做爲移動開發人員,也是憂心忡忡,在經理的指示下,開始Android信息安全防禦的旅程javascript
在如今安卓應用原生開發中,爲了追求開發的效率以及移植的便利性,使用WebView做爲業務內容展現與交互的主要載體是個不錯的折中方案。html
那麼在這種Hybrid(混合式) App中,不免就會遇到頁面JS須要與Java相互調用,調用Java方法去作那部分網頁JS不能完成的功能。網上的方法能夠告訴咱們這個時候咱們可使用addjavascriptInterface來注入原生接口到JS中,可是在安卓4.2如下的系統中,這種方案卻咱們的應用帶來了很大的安全風險。攻擊者若是在頁面執行一些非法的JS(誘導用戶打開一些釣魚網站以進入風險頁面),極有可能反彈拿到用戶手機的shell權限。接下來攻擊者就能夠在後臺默默安裝木馬,徹底洞穿用戶的手機java
==那麼如何避免呢?咱們從如下幾個方面進行優化webView.==android
提到對於Android4.2如下的JS任意代碼執行漏洞 , 還有包括證書版本太低不安全的因素 . 對於低版本 , 對於2019年將來來講,建議能夠放棄支持.web
我以爲這個頗有必要,不只僅是由於安全,包括微信公衆號,googleplay 都在強制要求開發者必須使用https算法
//若不需支持,則直接禁止 file 協議
setAllowFileAccess(false);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
複製代碼
因爲webView默認開啓密碼保存功能,因此在用戶輸入密碼時,會彈出提示框,詢問用戶是否保存。若選擇保存,則密碼會以明文形式保存到 ==/data/data/com.package.name/databases/webview.db==中,這樣就有被盜取密碼的危險。因此咱們應該禁止網頁保存密碼,設置shell
WebSettings.setSavePassword(false)
複製代碼
<manifest>
<meta-data
android:name="android.webkit.WebView.EnableSafeBrowsing"
android:value="true" />
<application> ... </application>
</manifest>
複製代碼
啓用安全瀏覽模式後,WebView 將參考安全瀏覽的惡意軟件和釣魚網站數據庫檢查訪問的 URL ,在用戶打開以前給予危險提示,體驗相似於Chrome瀏覽器 . 若是遇到不安全網站,會有如圖所示狀況 數據庫
這個也是重中之重,大多數的數據安全都是經過網絡攻擊形成的,那麼咱們如何去避免呢?編程
HTTPS的主要思想是在不安全的網絡上建立一安全信道,並可在使用適當的加密包和服務器證書可被驗證且可被信任時,對竊聽和中間人攻擊提供合理的防禦。能夠說是很是基礎的安全防禦級別了。後端
android中實現Https基本就這兩種方式,一種是不驗證證書,一種是有驗證證書(預防釣魚)。
第二種驗證證書稍微複雜一點,這種方式也只能簡單的防止釣魚,不能有效的防止釣魚。防止釣魚最終仍是靠用戶分辨,在正規渠道下載應用。
==可是若是把證書放在apk中也是一件很危險的事情,由於如今的反編譯技術不得不服,因此目前以爲最好的方式,就是放在.so文件中.==能夠進一步的下降風險 .
先後端進行自定義算法進行加密,md5 base64 AES RSA 等等,==算法推薦.so和java相互調用的形式==.
System.getProperty("http.proxyHost");
System.getProperty("http.proxyPort");
複製代碼
正常這兩行代碼獲取的是null,若是返回不爲空,就是掛代理了,那麼就能夠考慮是否不給數據了
道高一尺,魔高一丈,將來的路還會很長.
有了數據就得存放,若是存放,就會被別人發現.因此得存好嘍
在Andoid設備中,以'.'開頭文件或者文件夾是不可見的,而且也能夠進行讀寫操做.若是隱藏了存儲了文件位置,就能夠避免用戶誤操做和被發現的機會.
就算被找到,內容若是以密文的形式,也會下降被泄漏的風險
硬編碼很容易被反編譯找到.建議存儲到.so中(雖然.so也能夠被破解,可是相較於java更安全點)
儘可能存儲到手機內部存儲上,不要存儲到外部存儲卡上(由於手機內部存儲只對相應的應用開放,外部存儲對全部的應用都開放)
規範安卓標準組件(Activity、Service、Receiver、Provider)的訪問權限
exported屬性爲四大組件共有屬性,其中含義大同小異。默認值由其包含 ==== 與否決定。若未包含====,默認爲「false」,若存在至少一個====,則默認值爲「true」。
表示是否容許外部應用組件啓動。若爲「false」,則 Activity 只能由同一應用或同一用戶 ID 的不一樣應用啓動。
表示是否容許外部應用組件調用服務或與其進行交互。若爲「false」,則 Activity 只能由同一應用或同一用戶 ID 的不一樣應用啓動。
表示是否能夠接收來自其應用程序以外的消息,如來自系統或或其餘應用的廣播。若爲「 false」,則廣播接收器只能接收具備相同用戶ID的相同應用程序或應用程序的組件發送的消息。
表示是否容許其餘應用程序訪問內容提供器。若爲「false」,則具備與Provider相同的用戶ID(UID)的應用程序才能訪問它。若是須要給其餘應用程序提供內容,則應當限定讀寫權限。
自定義權限,限制本身的組件訪問,詳情看Android自定義權限與使用
區別基於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);
}
複製代碼
不少人說要在發佈的時候手動設置該值爲false,其實根據官方文檔說明,默認值就是false。
設置是否支持備份,默認值爲true,應當慎重支持該屬性,避免應用內數據經過備份形成的泄漏問題。
對於操做密碼等危險輸入的狀況,能夠考慮自定義鍵盤處理,防止經過鍵盤被盜用密碼.
多作一層安全措施,少一點風險.
自定義工具類,不要在線上出現敏感的信息
各類雲測平臺進行測試
判斷手機是否包含藍牙等模塊,一些信息是否跟手機真機不一致等.防止經過模擬器篡改信息
經過判斷簽名信息,防止,被二次打包,調試應用信息
帳號與相應設備進行綁定,若是發現與經常使用設備不符合,增長短信登陸形式進行從新登陸
重編譯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哈希值應該存放在服務器端。
爲了防止apk被動態調試,能夠檢測是否有調試器鏈接。在Application類中提供了isDebuggerConnected()方法用於檢測是否有調試器鏈接,若是發現有調試器鏈接,能夠直接退出程序。
檢測是否包含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;
}
複製代碼
檢測是否安裝有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;
}
複製代碼