以下內容摘自 阿里巴巴Android開發手冊
我們的目標是:
- 防患未然,提升質量意識,降低故障率和維護成本;
- 標準統一,提升協作效率;
- 追求卓越的工匠精神,打磨精品程式碼。
- 【強制】必須遵守,違反本約定或將會引起嚴重的後果;
- 【推薦】儘量遵守,長期遵守有助於系統穩定性和合作效率的提升;
- 【參考】充分理解,技術意識的引導,是個人學習、團隊溝通、專案合作的方向。
阿里Android開發規範:資原始檔命名與使用規範
阿里Android開發規範:四大基本元件
阿里Android開發規範:UI 與佈局
阿里Android開發規範:程式、執行緒與訊息通訊
阿里Android開發規範:檔案與資料庫
阿里Android開發規範:Bitmap、Drawable 與動畫
阿里Android開發規範:安全與其他
1、【強制】使用 PendingIntent 時,禁止使用空 intent,同時禁止使用隱式 Intent 說明:
- 使用 PendingIntent 時,使用了空 Intent,會導致惡意使用者劫持修改 Intent 的內容。禁止使用一個空 Intent 去構造 PendingIntent,構造 PendingIntent 的 Intent一定要設定 ComponentName 或者 action。
- PendingIntent 可以讓其他 APP 中的程式碼像是執行自己 APP 中。PendingIntent的intent接收方在使用該intent時與傳送方有相同的許可權。在使用PendingIntent時,PendingIntent 中包裝的 intent 如果是隱式的 Intent,容易遭到劫持,導致資訊洩露。
正例:
Intent intent = new Intent(this, SomeActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
try {
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
複製程式碼
反例 1:
Bundle addAccountOptions = new Bundle();
mPendingIntent = PendingTntent.getBroadcast(this, 0, new Intent, 0);
addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS,
Utils.hasMultipleUsers(this));
AccountManager.get(this).addAccount(accountType,
null,
null,
addAccountOptions,
null,
mCallback,
null);
複製程式碼
反例 2: mPendingIntent 是通過 new Intent()構造原始 Intent 的,所以為“雙無”Intent,這個PendingIntent最終被通過AccountManager.addAccount 方法傳遞給了惡意APP介面。
Intent intent = new Intent("com.test.test.pushservice.action.METHOD");
intent.addFlags(32);
intent.putExtra("app",msg);
PendingIntent.getBroadcast(this, 0, intent, 0));
複製程式碼
如上程式碼PendingIntent.getBroadcast,PendingItent中包含的Intent為隱式intent, 因此當 PendingIntent 觸發執行時,傳送的 intent 很可能被嗅探或者劫持,導致 intent 內容洩漏。 擴充套件參考:
2、【強制】禁止使用常量初始化向量引數構建 IvParameterSpec,建議 IV 通過隨機方式產生。
說明:
使用固定初始化向量,結果密碼文字可預測性會高得多,容易受到字典式攻擊。iv的作用主要是用於產生密文的第一個 block,以使最終生成的密文產生差異(明文相同的情況下),使密碼攻擊變得更為困難,除此之外 iv 並無其它用途。因此 iv 通過隨機方式產生是一種十分簡便、有效的途徑。
正例:
byte[] rand = new byte[16];
SecureRandom r = new SecureRandom();
r.nextBytes(rand);
IvParameterSpec iv = new IvParameterSpec(rand);
複製程式碼
反例:
IvParameterSpec iv_ = new IvParameterSpec("1234567890".getBytes());
System.out.println(iv_.getIV());
複製程式碼
3、 【強制】將 android:allowbackup 屬性設定為 false,防止 adb backup 匯出資料。
說明:
在 AndroidManifest.xml 檔案中為了方便對程式資料的備份和恢復在 Android APIlevel 8 以後增加了 android:allowBackup 屬性值。預設情況下這個屬性值為 true,故當 allowBackup 標誌值為 true 時,即可通過 adb backup 和 adb restore 來備份和恢復應用程式資料。
正例:
<application
android:allowBackup="false"
android:largeHeap="true"
android:icon="@drawable/test_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
複製程式碼
4、【強制】在實現的 HostnameVerifier 子類中,需要使用 verify 函式效驗伺服器主機名的合法性,否則會導致惡意程式利用中間人攻擊繞過主機名效驗。
說明:
在握手期間,如果 URL 的主機名和伺服器的標識主機名不匹配,則驗證機制可以回撥此介面的實現程式來確定是否應該允許此連線。如果回撥內實現不恰當,預設接受所有域名,則有安全風險。
反例:
HostnameVerifier hnv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// 總是返回 true,接受任意域名伺服器
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
複製程式碼
正例:
HostnameVerifier hnv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//示例
if("yourhostname".equals(hostname)){
return true;
} else {
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
return hv.verify(hostname, session);
}
}
};
複製程式碼
5、【強制】利用 X509TrustManager 子類中的 checkServerTrusted 函式效驗伺服器端證照的合法性。
說明:
在實現的 X509TrustManager 子類中未對服務端的證照做檢驗,這樣會導致不被信任的證照繞過證照效驗機制。
反例:
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//do nothing,接受任意客戶端證照
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//do nothing,接受任意服務端證照
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
複製程式碼
6、【強制】META-INF 目錄中不能包含如.apk,.odex,.so 等敏感檔案,該資料夾沒有經過簽名,容易被惡意替換。
7、【強制】Receiver/Provider 不能在毫無許可權控制的情況下,將 android:export 設定為 true。
8、【參考】資料儲存在 Sqlite 或者輕量級儲存需要對資料進行加密,取出來的時候進行解密。
9、 【強制】阻止 webview 通過 file:schema 方式訪問本地敏感資料。
10、【強制】不要廣播敏感資訊,只能在本應用使用 LocalBroadcast,避免被別的應用
收到,或者 setPackage 做限制。
11、【強制】不要把敏感資訊列印到 log 中。
說明:
在 APP 的開發過程中,為了方便除錯,通常會使用 log 函式輸出一些關鍵流程的資訊,這些資訊中通常會包含敏感內容,如執行流程、明文的使用者名稱密碼等,這會讓攻擊者更加容易的瞭解 APP 內部結構方便破解和攻擊,甚至直接獲取到有價值的敏感資訊。
反例:
String username = "log_leak";
String password = "log_leak_pwd";
Log.d("MY_APP", "usesname" + username);
Log.d("MY_APP", "password" + password, new Throwable());
Log.v("MY_APP", "send message to server ");
複製程式碼
以上程式碼使用 Log.d Log.v 列印程式的執行過程的 username 等除錯資訊,日誌沒有關閉,攻擊者可以直接從 Logcat 中讀取這些敏感資訊。所以在產品的線上版本中關閉除錯介面,不要輸出敏感資訊。
12、【強制】對於內部使用的元件,顯示設定元件的"android:exported"屬性為 false。
說明:
Android 應用使用 Intent 機制在元件之間傳遞資料,如果應用在使用 getIntent(),getAction(),Intent.getXXXExtra()獲取到空資料、異常或者畸形資料時沒有進行異常捕獲,應用就會發生 Crash,應用不可使用(本地拒絕服務)。惡意應用可通過向受害者應用傳送此類空資料、異常或者畸形資料從而使應用產生本地拒絕服務。
13、【強制】應用釋出前確保 android:debuggable 屬性設定為 false。
14、【強制】使用 Intent Scheme URL 需要做過濾。
說明:
如果瀏覽器支援 Intent Scheme Uri 語法,如果過濾不當,那麼惡意使用者可能通過瀏覽器 js 程式碼進行一些惡意行為,比如盜取 cookie 等。如果使用了 Intent.parseUri函式 , 獲取的 intent 必須嚴格 過濾 , intent 至少包含addCategory(“android.intent.category.BROWSABLE”) , setComponent(null) ,setSelector(null)3 個策略。
正例:
// 將 intent scheme URL 轉換為 intent 物件
Intent intent = Intent.parseUri(uri);
// 禁止沒有 BROWSABLE category 的情況下啟動 activity
intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
intent.setSelector(null);
// 使用 intent 啟動 activity
context.startActivityIfNeeded(intent, -1)
複製程式碼
反例:
Intent intent = Intent.parseUri(uri.toString().trim().substring(15), 0);
intent.addCategory("android.intent.category.BROWSABLE");
context.startActivity(intent);
複製程式碼
擴充套件參考:
15、【強制】金鑰加密儲存或者經過變形處理後用於加解密運算,切勿硬編碼到程式碼中。
說明:
應用程式在加解密時,使用硬編碼在程式中的金鑰,攻擊者通過反編譯拿到金鑰可以輕易解密 APP 通訊資料。
16、【強制】將所需要動態載入的檔案放置在 apk 內部,或應用私有目錄中,如果應用必須要把所載入的檔案放置在可被其他應用讀寫的目錄中(比如 sdcard),建議對不可信的載入源進行完整性校驗和白名單處理,以保證不被惡意程式碼注入。
17、【強制】除非 min API level >=17,請注意 addJavascriptInterface 的使用。
說明:
API level>=17,允許 js 被呼叫的函式必須以@JavascriptInterface 進行註解,因此不受影響; 對於 API level < 17,儘量不要使用 addJavascriptInterface,如果一定要用,那麼:
- 使用 https 協議載入 URL,使用證照校驗,防止訪問的頁面被篡改掛馬;
- 對載入 URL 做白名單過濾、完整性校驗等防止訪問的頁面被篡改;
- 如果載入本地 html,應該會 HTML 內建在 APK 中,以及對 HTML 頁面進行完整性校驗。
18、【強制】使用 Android 的 AES/DES/DESede 加密演算法時,不要使用預設的加密模式ECB,應顯示指定使用 CBC 或 CFB 加密模式。 說明: 加密模式 ECB、CBC、CFB、OFB 等,其中 ECB 的安全性較弱,會使相同的銘文在不同的時候產生相同的密文,容易遇到字典攻擊,建議使用 CBC 或 CFB 模式。
- ECB:Electronic codebook,電子密碼本模式
- CBC:Cipher-block chaining,密碼分組連結模式
- CFB:Cipher feedback,密文反饋模式
- OFB:Output feedback,輸出反饋模式
19、【強制】不要使用 loopback 來通訊敏感資訊。
20、【推薦】對於不需要使用 File 協議的應用,禁用 File 協議,顯式設定 webView.getSettings().setAllowFileAccess(false),對於需要使用 File 協議的應用,禁止 File協議呼叫 JavaScript,顯式設定 webView.getSettings().setJavaScriptEnabled(false)。
21、【強制】Android APP 在 HTTPS 通訊中,驗證策略需要改成嚴格模式。 說明:Android APP 在 HTTPS 通訊中,使用 ALLOW_ALL_HOSTNAME_VERIFIER,表示允許和所有的 HOST 建立 SSL 通訊,這會存在中間人攻擊的風險,最終導致敏感資訊可能會被劫持,以及其他形式的攻擊。
反例:
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
複製程式碼
ALLOW_ALL_HOSTNAME_VERIFIER 關閉 host 驗證,允許和所有的 host 建立SSL 通訊,BROWSER_COMPATIBLE_HOSTNAME_VERIFIER 和瀏覽器相容的驗證策略,即萬用字元能夠匹配所有子域名 ,STRICT_HOSTNAME_VERIFIER 嚴格匹配模式,hostname 必須匹配第一個 CN 或者任何一個 subject-alts,以上例子使用了 ALLOW_ALL_HOSTNAME_VERIFIER,需要改成 STRICT_HOSTNAME_VERIFIER。
22.【推薦】Android5.0 以後安全性要求較高的應用應該使用 window.setFlag(LayoutParam.FLAG_SECURE) 禁止錄屏。
23.【推薦】zip 中不建議允許../../file 這樣的路徑,可能被篡改目錄結構,造成攻擊。 說明:當 zip 壓縮包中允許存在"../"的字串,攻擊者可以利用多個"../"在解壓時改變 zip 檔案存放的位置,當檔案已經存在是就會進行覆蓋,如果覆蓋掉的檔案是 so、dex 或者 odex 檔案,就有可能造成嚴重的安全問題。
正例:
對路徑進行判斷,存在".."時丟擲異常。
//對重要的 Zip 壓縮包檔案進行數字簽名校驗,校驗通過才進行解壓
String entryName = entry.getName();
if (entryName.contains("..")){
throw new Exception("unsecurity zipfile!");
}
複製程式碼
反例:
BufferedOutputStream dest = null;
try {
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream("/Users/yunmogong/Documents/test/test.zip")));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null){
int count;
byte data[] = new byte[BUFFER];
String entryName = entry.getName();
FileOutputStream fos = new FileOutputStream(entryName);
//System.out.println("Extracting:" + entry);
dest = new BufferedOutputStream(fos, BUFFER);
while ((count=zis.read(data,0,BUFFER)) != -1){
dest.write(data, 0, count);
}
dest.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
dest.close();
} catch (IOException e) {
e.printStackTrace();
}
}
複製程式碼
如上程式碼,沒有對檔案的路徑名進行判斷直接進行解壓,如果路徑中包含../字串,就會造成目錄的遍歷問題,一旦遭到中間人攻擊替換下載的檔案,將會導致某些惡意檔案被執行。
24、【強制】開放的 activity/service/receiver 等需要對傳入的 intent 做合法性校驗。
25、【推薦】加密演算法:使用不安全的 Hash 演算法(MD5/SHA-1)加密資訊,存在被破解的風險,建議使用 SHA-256 等安全性更高的 Hash 演算法。
26、【推薦】Android WebView 元件載入網頁發生證照認證錯誤時,採用預設的處理方法handler.cancel(),停止載入問題頁面。
說明:
Android WebView 元件載入網頁發生證照認證錯誤時,會呼叫 WebViewClient 類的onReceivedSslError 方法,如果該方法實現呼叫了 handler.proceed()來忽略該證照錯誤,則會受到中間人攻擊的威脅,可能導致隱私洩露.
反例:
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new JsBridge(mContext), JS_OBJECT);
mWebView.loadUrl("http://www.example.org/tests/addjsif/");
mWebView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); // 忽略 SSL 證照錯誤
}
});
複製程式碼
27、【推薦】直接傳遞命令字或者間接處理有敏感資訊或操作時,避免使用 socket 實現, 使用能夠控制許可權校驗身份的方式通訊。
其他
1、【強制】不要通過 Msg 傳遞大的物件,會導致記憶體問題。 2、【強制】不能使用 System.out.println 列印 log。 正例:
Log.d(TAG, "Some Android Debug info ...");
複製程式碼
反例:
System.out.println("System out println ...");
複製程式碼
3、【強制】Log 的 tag 不能是" "。 說明: 日誌的 tag 是空字串沒有任何意義,也不利於過濾日誌。 正例:
private static String TAG = "LoginActivity";
Log.e(TAG, "Login failed!");
複製程式碼
反例:
Log.e("", "Login failed!");
複製程式碼
阿里Android開發規範:資原始檔命名與使用規範
阿里Android開發規範:四大基本元件
阿里Android開發規範:UI 與佈局
阿里Android開發規範:程式、執行緒與訊息通訊
阿里Android開發規範:檔案與資料庫
阿里Android開發規範:Bitmap、Drawable 與動畫
阿里Android開發規範:安全與其他