當前位置:
首頁 > 知識 > 開發更安全的安卓應用要注意哪些?

開發更安全的安卓應用要注意哪些?

安卓應用常被用於處理非常敏感的數據。開發者有責任確保用戶提供的信息不被居心不良者輕易截取。開放式 Web 應用安全項目(OWASP)[9,10]嘗試著列舉移動應用潛在的安全問題。

其中一些是系統架構師的責任(例如弱伺服器端控制有關的問題),一些是後端開發者的責任(授權檢查相關問題),最後一些就是純粹與移動應用本身有關。本文我們將關注通過安卓開發者努力可以解決的問題。

因此我們將在這裡提出三個潛在的漏洞源:使用網路服務(WS)通信時的風險、在存儲設備上存儲數據時潛在的信息泄漏以及第三方軟體能輕易編輯應用程序的漏洞。

一、安全的網路服務調用

對於使用WS的敏感應用,最重要的就是保證用戶分享在後台的數據是安全的。事實上,如果網路上的請求能被輕易截取,最安全的應用程序也是毫無用處的。

威脅:中間人攻擊(MITM)

應用程序遭受中間人攻擊有兩個主要的風險。

1.信息泄漏

如果竊取者控制了用戶使用應用的本地網路,他暗地裡輕易就能截取到 app 和 WS 的所有通信。

2.網路服務(WS)模仿

對 WS 有一定認識的人可以阻塞應用調用並且提供偽造的回應,這時用戶認為他們的請求已經執行,然而請求根本沒有到達後台。

測試應用在中間人攻擊時的脆弱性相當簡單:你只需要使用一個代理軟體(例如CharlesProxy[12]),然後建立設備來使用安裝了這個代理的機器。如果應用不能阻止中間人攻擊,你將能看到它執行的每個請求。現在,想像你的 app 用戶通過「不安全」的網路連接到你的網路服務:竊取者可以毫不費力地將代理安裝在網路路由器上,就能嗅到所有不加密的請求。

攻擊源:TLS/SSL證書鏈

保證通信安全至少要使用 HTTPS 協議,也就是說使用安全傳輸層協議(TLS)或是它的前身安全套接層協議(SSL)加密的通信。

然而,如果有必要,這並不是系統需要遵守的唯一條件。為了理解清楚,我們來看看 SSL 協議的工作原理。

SSL 證書(至少)包括三個證書:

根證書。這是由證書認證機構(CA)頒發的,也就是一個可以確保整個通信時安全的值得信任的組織。

中間證書。有許多個中間證書。它們建立終端用戶證書和根證書的連接,是為提供WS的伺服器服務,由根證書籤名的證書。

終端用戶證書。終端用戶證書是適用於WS物理伺服器的證書。

安卓 SSL 本地保護:

安卓網路層有一個內置的CA 證書列表(超過一百個,你可以在設備參數中檢查這個列表)。每個HTTPS網路調用需要在證書鏈上有一個CA證書。

然而,沒有辦法確保其餘的鏈適合我們想要連接的伺服器。例如一個竊取者可以向CA購買中間證書實施中間人攻擊。所有的網路事務都將被系統視作無效。這種漏洞非常常見:一個研究表明[1]73%使用HTTPS 協議的應用並沒有用正確的方式檢查證書。

怎樣保證連接的是我們的後台並且這個連接是安全?

解決上述問題的方式是手動檢查中間證書(適用於特定伺服器)是已知證書。這意味著我們需要將特定的伺服器證書存儲在應用中,可以將它作為常數存儲在資源文件或直接放在源代碼中來實現。

我們可能會疑惑為什麼需要檢查中間證書而不是終端用戶。這裡有兩個理由,第一,終端用戶證書生存期很短。第二點是安全理由:想像一下,一個黑客完全控制了系統,那他將擁有你的私鑰(伺服器需要私鑰簽名請求)。應用會認為請求是由正確的終端用戶私鑰簽名,並且會允許連接。如果認證在中間伺服器檢查時實現,那就有可能通過聯繫中間CA遠程廢除證書。

SSLSocketFactory 類可以驗證一個 SSL 連接是否安全。創建一個執行中間證書檢查的 SSLSocketFactory,我們需要執行以下步驟。

publicclassMyX509TrustManagerimplementsX509TrustManager{

privateX509Certificatecertificate;

publicMyX509TrustManager(InputStreamknownIntermediateCertificate)throwsCertificateException{

CertificateFactorycertFactory=CertificateFactory.getInstance("X.509");

certificate=certFactory.generateCertificate(knownIntermediateCertificate);

}

@Override

publicvoidcheckClientTrusted(X509Certificate[]chain,StringauthType)throwsCertificateException{

// Do nothing. We only want to check server side certificate.

}

@Override

publicvoidcheckServerTrusted(X509Certificate[]chain,StringauthType)throwsCertificateException{

// Verify that the certificate domain name matches the expected one

if(!chain[].getIssuerDN().equals(certificate.getSubjectDN())){

thrownewCertificateException("Parent certificate of server was different than expected signing certificate");

}

try{

// Verifiy that the certificate key matches the expected one

chain[].verify(certificate.getPublicKey());

// Verify that the certificate has not expired

chain[].checkValidity();

}catch(Exceptione){

thrownewCertificateException("Parent certificate of server was different than expected signing certificate");

}

}

@Override

publicX509Certificate[]getAcceptedIssuers(){

// Do nothing

returnnewX509Certificate[];

}

}

2.設置一個新的默認 SSLSocketFactory。代碼需要在網路調用前運行。

TrustManager[]trustManagerArray=newTrustManager[1];

MyTrustManagertrustManager=newMyTrustManager(TRUSTED_CERTIFICATE);

trustManagerArray[]=trustManager;

finalSSLContextsslc;

// TLS is the last SSL protocol and is used by all the CA

sslc=SSLContext.getInstance("TLS");

// We only need to give a TrustManager list as we don"t need to perform client authentification

sslc.init(null,trustManagers,null);

HttpsURLConnection.setDefaultSSLSocketFactory(sslc.getSocketFactory());

證書檢查潛在缺陷

1.中間證書會過期(其生存期大概是10年)。在證書過期前將新的證書添加到白名單可以有效檢測證書的變化。

2.中間證書認證機構會失效。如果中間證書認證機構失效,它所提供的安全機制將會完全無效。事實上,如果黑客保留了中間 CA ,他將能夠偽造證書鏈,擁有和你的證書鏈相同的中間證書。此時他就能實施中間人攻擊。儘管CA理論上是安全的,依然可能發生像2011年Dignotar證書失效的事件。一旦發生這種情況,唯一能做的事情就是改變伺服器上的SSL證書鏈,並且發布一個內嵌新的中間證書的版本。

3.SSLSocketFactory 認為它的策略適用於應用中的所有網路調用。如果sdk是內置的,那就有必要為遠程伺服器的這些sdk嵌入中間證書。存在的不確定性是無法輕易察覺伺服器證書的變化。

動態植入證書會遇到這種問題。應用只允許一個證書(主伺服器一個)並且在應用運行時獲取一個授權中間證書的動態列表,然後將這些證書添加到 SSLContext 的 trust manager中。

總而言之,在大多數情況下,中間檢查機制能夠防禦中間人攻擊。黑客竊聽通信時,他必須將自己的證書列入證書鏈, TrustManager不能識別這個證書,於是拒絕 HTTPS 連接。

二、設備上的存儲安全

多虧了SharedPreferences 介面,安卓平台提供了一種方便的方式存儲參數以及大文件。儘管存儲在shared preferences中的數據隱藏在隱藏目錄中,如果設備獲得了root許可權,那就可以輕而易舉地恢複數據。

因此,如果存儲在應用中的是敏感數據,有必要加密shared preferences中的數據。有兩種方式:

1.使用密碼庫加密/解密 SharedPreferences 的 values(最後是key)。有許多形式的 java 密碼庫,例如 javax.crypto、Bouncycastle[2] 和 Concealed[3]。

2.使用提供 SharedPreferences 包裝類的庫。這些密碼庫使用很方便,開發者不需要關心使用什麼演算法。然而,使用這些庫會失去靈活性並且有些並不使用安全演算法。因此,它們並不適合存儲敏感的數據。SecurePreference[4] 是其中一個最常用的提供這種包裝特性的庫。如果你選擇這種方式,你可以用非常直接的方式實例化一個繼承自SharedPreferences 的 SecurePreferences 類:

SecurePreferencessecurePreferences=newSecurePreferences(context,"MyPassword",null);

這兩種方法是基於例如AES(有一個合適的key 大小)的對稱密碼學。帶來的疑問是:我們應該使用哪種key?事實上,如果我們使用一個靜態key,可以通過反編譯應用解密參數。所以,最好的解決方式是在應用啟動時使用用戶輸入的pin碼/通行碼。或者使用 Fingerprint API [15](需要API 23以上版本),可以提供一種安全流暢的認證方式。

不幸的是,這種方法不能滿足每個應用的用戶體驗。例如如果我們想要在pin碼輸入前顯示存儲的信息,那我們就不能使用安全加密系統。

幸好,安卓提供了一種安全的方式來為一些應用程序/設備生成特定的key: KeyStore。安卓 KeyStore 是為了允許應用將私鑰放在不能被其他應用的地方,或者是不能通過實質性訪問存儲在設備上的數據獲取私鑰的地方。

機制非常簡單:首先,運行應用檢查應用相關的私鑰是否存在。如果不存在,就生成一個並存儲在KeyStore 中。如果私鑰存在,它可以成為安全密鑰來解析SharedPreferences 的數據,這多虧了上文描述的演算法。

Obaro Ogbo寫了一篇詳細的文章深刻描繪了如何使用 KeyStore 生成私鑰/公鑰對。KeyStore 主要的缺點是只允許API18以上版本使用。但是有一個兼容API14以上版本的補丁庫(這不是「官方」的布丁,所以你必須自己承擔後果)。

因此,我們建議當需要決定優先使用哪種系統時可以參考下面的策略圖:

三、防止應用被源代碼分析和修改

有時,安卓開發者不希望應用被其他人分析,解讀,最後被修改。這種要求有各種理由:

我們不希望黑客移除用於阻止非付費用戶使用某些功能的應用鎖。

我們開發的敏感應用時的風險是,黑客會將應用修改成所有輸入信息都會返回給他。儘管應用商店不容易發生這種情況,但是用戶可以去許多其他地方下載到偽造的應用,這些應用會用完全透明的方式偷取他所有的數據。

每個安卓開發者都應該注意,當開發敏感應用時,經驗豐富的人反編譯一個安卓應用是相當簡單的。特別是使用「本地」應用配置的情況。事實上,因為大多數安卓app的特性(使用 Java 位元組碼),反編譯位元組碼後解讀,改正,最後重建一個修改過的應用非常簡單。

這部分我們會強調一些可以規避風險的技術工具和構建原則。同時,我們需要注意,因為應用程序運行在客戶端設備上,我們並沒有百分之百確信的方法來規避這些風險。

1.有價值的演算法寫在伺服器端

下面是構建指南。如果你的應用程序價值是以演算法為基礎的,你當然不希望有人能輕易解讀、複製並且將你的演算法嵌入自己的應用程序中。此時,最好的解決方式是在伺服器端實現演算法。應用程序只需提供待處理的數據給伺服器,然後獲取演算法的返回值。這種結構的明顯缺點是,在離線狀態下無法使用app 的核心功能。

2.防止WS完全開放

如果應用程序的功能是藉助 WS 獲取數據,你可以發送在認證階段獲得的session token 或者在每個請求中授予user / password 來保證WS 安全。如果僅僅是在app 參數中使用認證標誌,將這個標誌設置成「always connected」就可以輕鬆修改應用程序代碼。這麼做的風險是用戶需要定期輸入使用過的id和密碼來延長會話。

3.使用Proguard混淆代碼

Proguard 是 Java 工程中常用的工具。Proguard 工具執行三個操作:壓縮步驟(移除無用代碼)、優化步驟(內聯方法、移除無用方法等)、混淆步驟。在最後一個步驟中,Proguard 會重命名Java 文件中所有類、屬性、方法名,為了使它們在位元組碼被反編譯後難以辨認。Proguard 當然也確保 JVM 能夠區分不同的編譯元素。

這個工具的有趣之處在於它使反編譯位元組碼可讀性變差了很多。然而,儘管代碼元素被重命名,通過逆向工程化應用的方式依然可能猜測出混淆後的方法和屬性的功能。Proguard 也生成了一個 mapping 文件,可以將混淆的 stacktrace 轉換成可讀狀態[6]。

網上有許多詳細解釋如何配置Proguard 的指導,比如在安卓系統文檔中的配置[7]。

4.使用編譯庫

通過Java本地介面(JNI),我們可以使用 C/C++ 語言寫的本地代碼(已編譯代碼)並且與Java 代碼交互。開發安卓程序時會更簡單,因為 NDK 提供了可以在應用程序中使用已編譯代碼的工具。整體機制很簡單:編譯你的 C/C++代碼(必須包含標準的JNI 入口點),獲得一個 .so 文件。這時你就在應用程序工程中包含了庫和java 介面。

編譯庫的主要優勢是,反編譯代碼可讀性更差,因為 .so 文件使用本地機器語言而不是java 位元組碼。用 C/C++ 開發應用程序高度敏感的部分(例如最高機密演算法或是安全層)然後與其餘用傳統 Java 編寫的部分交互,這將是一個不錯的實踐(如果實際上很方便)。

使用 NDK 依然有許多缺點:我們必須為應用程序針對的不同類型的硬體結構編譯本地庫,這樣就放棄了產生 crash 時得到適當stacktrace 的可能性,同時它也大大增加了代碼結構。

總結

在本文,我們建議的解決方法覆蓋了 3 個 OWASP 中排名前十的手機安全問題[9]。像我們介紹中提到的,只有當系統結構是安全的,應用程序才是安全的。一個人可以開發技術上安全的應用,但如果伺服器沒有授權良好的認證系統,所有的努力都是無意義的。同時,確保完美的安全邊界是手機開發者的責任,這篇文章給出了覆蓋安全邊界的解決方法。

參考

[1] https://www.fireeye.com/blog/threat-research/2014/08/ssl-vulnerabilities-who-listens-when-android-applications-talk.html

[3] https://code.facebook.com/posts/1419122541659395/introducing-conceal-efficient-storage-encryption-for-android/

[4] https://github.com/scottyab/secure-preferences

[5] http://geeknizer.com/decompile-reverse-engineer-android-apk/

[6] http://proguard.sourceforge.net/manual/retrace/examples.html

[7] http://developer.android.com/tools/help/proguard.html

[8] http://www.javaworld.com/article/2076513/java-concurrency/enhance-your-java-application-with-java-native-interface–jni-.html

[9] https://www.owasp.org/index.php/OWASP_Mobile_Security_Project#tab=Top_10_Mobile_Risks

[10] https://www.owasp.org/index.php/About_OWASP

[11] http://www.androidauthority.com/use-android-keystore-store-passwords-sensitive-information-623779/

[13] https://threatpost.com/final-report-diginotar-hack-shows-total-compromise-ca-servers-103112/77170/

[14] https://github.com/pprados/android-keychain-backport

[15] https://developer.android.com/about/versions/marshmallow/android-6.0.html#fingerprint-authentication


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員之家 的精彩文章:

身為程序員,你是怎麼跟非程序員解釋編程的?
你問程序員為什麼錢多話少死得早?來 我告訴你!

TAG:程序員之家 |