Android代碼安全開發指導


隨着移動通訊技術的發展,移動終端不斷朝向智能化方向邁進。手機移動終端做爲移動互聯網時代最主要的載體,其安全性正面臨嚴峻的挑戰。本Android代碼安全開發指導規範,旨在最大程度減小安全隱患,並統一規範軟件開發在功能實現、編碼階段的行爲。javascript


1、適用範圍

本規範規定了Android平臺軟件開發的編碼設計與實現。
php


2、組件安全

Android組件安全此處共涉及以下三方面風險:html

2.1 導出組件拒絕服務風險

場景介紹java

應用在AndroidManifest.xml文件中設置了Activity/Service/Broadcast/Provider爲導出,致使該組件能夠被第三方程序調用,並能夠經過Intent接受參數傳入。若是這些組件在從Intent獲取參數的時候沒有對其合法性進行校驗,且代碼沒有使用異常處理,則會致使應用拋出異常沒法被捕獲,進而致使應用崩潰。第三方惡意程序能夠經過在後臺不斷髮送可以使應用崩潰的Intent,使得程序沒法正常運行。android

風險分析nginx

組件沒有進行權限管控,被外部非法訪問;web

組件沒有對Intent參數進行校驗或異常處理,致使應用崩潰。算法

正確措施typescript

確認組件是否處於外部可調用狀態;判斷組件是否有必要公開,如無必要,則必須設置爲非導出狀態(exported爲false);公開組件只要是getXXXExtra(),加上try catch捕獲異常。數據庫

錯誤案例

非必要公開組件exported設置爲true:

<activity android:name=".AuthActivity" android:exported="true"> ...</activity>

公開組件未作異常處理:

if(i.getAction().equals("serializable_action")){  i.getSerializableExtra("serializable_key"); //未作異常判斷 }

正確案例

非必要公開組件exported設置爲false:

<activity android:name=".AuthActivity" android:exported="false"> ...</activity>

公開組件必須作異常處理:

Try{ ....xxx.getXXXExtra()....}Catch Exception{ //異常處理}

2.2 導出組件Content Provider數據安全風險

場景介紹

應用在AndroidManifest.xml文件中設置了Content Provider爲導出,致使該組件能夠被第三方程序調用,並可使用SQL語句進行數據查詢。若是Content Provider中存儲了敏感數據,如配置文件、用戶敏感信息等,可能會致使Content Provider本地數據泄漏。若是應用使用外部參數構造SQL查詢語句的時候沒有進行處理,會產生SQL注入漏洞,致使執行惡意的SQL語句,產生數據泄露、數據惡意刪除、惡意修改等風險。若是沒有對Content Provider組件的訪問進行權限控制和對訪問的目標文件的Content Query Uri進行有效判斷,攻擊者利用該應用暴露的Content Provider的openFile()接口進行文件目錄遍歷以達到訪問任意可讀文件的目的。

  風險分析

Content Provider未對組件的訪問進行權限控制與SQL注入過濾的話,可能致使信息泄露,SQL注入,目錄遍歷等數據泄露風險。

  正確措施

確認組件是否處於外部可調用狀態;判斷組件是否有必要公開,如無必要,則必須設置爲非導出狀態(exported爲false)。不要使用字符串拼接的形式構造SQL查詢語句,而是使用一個用於將?做爲可替換參數的選擇子句以及一個單獨的選擇參數數組。執行此操做時,用戶輸入直接受查詢約束,而不解釋爲 SQL 語句的一部分。根據業務須要,移除沒有必要的openFile()接口。

// 錯誤寫法 未對Uri.decode路徑進行參數處理private static String IMAGE_DIRECTORY = localFile.getAbsolutePath();public ParcelFileDescriptor openFile(Uri paramUri, String paramString) throws FileNotFoundException { File file = new File(IMAGE_DIRECTORY, paramUri.getLastPathSegment()); return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);}

  正確案例

private static String IMAGE_DIRECTORY = localFile.getAbsolutePath(); public ParcelFileDescriptor openFile(Uri paramUri, String paramString) throws FileNotFoundException { String decodedUriString = Uri.decode(paramUri.toString());File file = new File(IMAGE_DIRECTORY, Uri.parse(decodedUriString).getLastPathSegment());//對Uri.decode路徑進行參數處理if (file.getCanonicalPath().indexOf(localFile.getCanonicalPath()) != 0) { throw new IllegalArgumentException(); } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }

2.3 Intent Scheme URL漏洞攻擊  

場景介紹

Intent Scheme URI是一種特殊的URL格式,用來經過Web頁面啓動已安裝應用的Activity組件。

風險分析

當使用webview應用,若是處理Intent Scheme Uri不當,可用JS代碼進行惡意行爲。

正確措施

Intent.parseUri函數,比較安全的使用Intent Scheme URI方法是,若是使用了Intent.parseUri函數,獲取的intent必須嚴格過濾,intent至少包含如下三個策略:

addCategory("android.intent.category.BROWSABLE")

setComponent(null)

setSelector(null)

錯誤案例

Intent intent = Intent.parseUri(uri.toString().trim().substring(15),0);intent.addCategory(「android.intent.category.BROWSABLE」);context.startActivity(intent);

正確案例

// 將intent的URI(intent scheme URL)轉換爲intent對象Intent intent = Intent.parseUri(uri);// 禁止在沒有設置可瀏覽的目錄(BROWSABLE category)的時候啓動活動intent.addCategory("android.intent.category.BROWSABLE");// 禁止顯式調用(explicit call)intent.setComponent(null);// 禁止intent的選擇器(selector)intent.setSelector(null);// 經過intent啓動活動context.startActivityIfNeeded(intent, -1)


3、權限使用 

3.1 protectionLevel 配置

場景介紹

應用經過自定義權限的protectionLevel屬性配置,受權其它應用使用。

風險分析

若是應用自定義權限的protectionLevel屬性設置不當,會暴露更多的接口和敏感信息,致使被惡意攻擊、提權。

正確措施

若是應用須要聲明新權限給其它應用使用,優先考慮建立「signature」保護級別的權限,不可建立「dangerous」保護級別。

錯誤案例

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.bright.permission"><permission android:name="com.bright.permission.TEST" android:description="" android:permissionGroup="com.bright.permission-group.TEST" android:protectionLevel="dangerous"/>...</manifest>

正確案例

Signatrue:若是申請權限的應用和聲明權限的應用有相同的證書,那麼系統就自動授予這個應用權限。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.bright.permission"><permission android:name="com.bright.permission.TEST" android:description="" android:permissionGroup="com.bright.permission-group.TEST" android:protectionLevel="signature"/>...</manifest>

3.2 應用濫用系統權限

場景介紹

普通應用設置sharedUserId和選擇簽名文件時,採用android.uid.system或平臺簽名致使權限擴大。

風險分析

應用若是使用平臺簽名或者shareuid,尤爲是系統uid,系統將得到較大權限。若遭受攻擊,容易致使惡意代碼越權執行。

正確措施

若非特殊的需求,應用不將sharedUserId設置爲android.uid.system,不使用平臺籤名。


4、存儲安全

4.1 文件全局讀寫風險

場景介紹

應用建立或訪問私有目錄文件。

風險分析

應用建立或訪問私有目錄文件將其設置爲全局可讀或可寫,會存在惡意讀取文件內容的隱患。

正確措施

應用避免使用MODE_WORLD_WRITEABLE或MODE_WORLD_READABLE模式存儲敏感數據。

錯誤案例

FileOutputStream fos = openFileOutput("private_data.txt", Context.MODE_WORLD_WRITEABLE); // 風險代碼,數據訪問模式全局可寫SharedPreferences prefs = getSharedPreferences("data", Context.MODE_WORLD_READABLE); // 風險代碼,數據訪問模式全局可讀

正確案例

FileOutputStream fos = openFileOutput("private_data.txt", Context.MODE_PRIVATE);SharedPreferences prefs = getSharedPreferences("data", Context.MODE_PRIVATE); 

4.2 文件路徑硬編碼

場景介紹

應用建立和訪問文件。

風險分析

當應用被逆向,文件路徑硬編碼會容易被識別,存在安全隱患。

正確措施

經過Android系統相關API接口獲取對應的目錄。

錯誤案例

public File getDir(String alName) { File file = new File("/mnt/sdcard/Download/Album", alName); // 風險代碼,文件路徑硬編碼 if (!file.mkdirs()) { Log.e(LOG_TAG, "Directory not created"); } return file;}

正確案例

public File getDir(String alName) { File file = new File(Environment.getExternalStoragePublicDirectory(Environment. DIRECTORY_PICTURES), alName); if (!file.mkdirs()) { Log.e(LOG_TAG, "Directory not created"); } return file; }

4.3 應用間文件共享

場景介紹

應用之間共享文件。

風險分析

應用間共享文件,經過放寬文件系統權限方法實現,存在安全隱患。

正確措施

經過FileProvider實現應用間共享文件。

錯誤案列

void getAlbumImage(String imagePath) { File image = new File(imagePath); Intent getAlbumImageIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(image)); startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE);}

  正確案例

<!-- AndroidManifest.xml --><manifest> ... <application> ... <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider> ... </application> </manifest>
<!-- res/xml/provider_paths.xml --><paths> <files-path path="album/" name="myimages" /></paths>
void getAlbumImage(String imagePath) { File image = new File(imagePath); Intent getAlbumImageIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Uri imageUri = FileProvider.getUriForFile( this, "com.example.provider", image); getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE);}


5、傳輸安全

5.1 HTTPS鏈接檢測 

  場景介紹

Http沒有使用TLS1.2以上安全協議。

  風險分析

http存在的風險:

數據被監聽竊取:經過軟件抓包,能夠看到請求的內容。

數據被篡改: 請求的數據被修改。

中間人重放攻擊:數據被中間人監聽到,再次發送給服務器。

  正確措施

應用經過HTTPS訪問服務器。

  錯誤案例

// HttpURLConnectionURL url = new URL("http://www.baidu.com");connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("GET");connection.setConnectTimeout(5000);connection.setReadTimeout(5000);InputStream in = connection.getInputStream();connection.disconnect();// OKHttpOkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url("http://www.baidu.com").build();try {Response response = client.newCall(request).execute();String result = response.body().string();Log.d(TAG, "result: "+result);show(result);} catch (IOException e) {e.printStackTrace(); }

  正確案例

public static void initHttpsURLConnection(String password, String keyStorePath, String trustStorePath) throws Exception { SSLContext sslContext = null; HostnameVerifier hnv = new MyHostnameVerifier(); try { sslContext = getSSLContext(password, keyStorePath, trustStorePath); } catch (GeneralSecurityException e) { e.printStackTrace(); } if (sslContext != null) { HttpsURLConnection.setDefaultSSLSocketFactory(sslContext .getSocketFactory()); } HttpsURLConnection.setDefaultHostnameVerifier(hnv);}// OKHttpUri uri = Uri.parse("https://www.example.com") .buildUpon() .appendQueryParameter("name", query) .appendQueryParameter("category", "student") .build();URL url = buildURL(uri);Request request = buildRequest(url).build();

5.2 Socket未認證身份風險

  場景介紹

Socket層通訊,須要啓用SSLSocket實現身份驗證和加密。

  風險分析

Socket未認證身份,容易致使數據在傳輸過程當中被竊取或者篡改。

  正確措施

考慮到android設備會常常鏈接到一些開放網絡,全部經過網絡通訊的應用必須保證協議安全,建議Socket層通訊都使用SSLSocket類實現身份驗證和加密。

  錯誤案例

Socket socket = new Socket("xxx.xxx.xxx.xxx", xxx); OutputStream os = socket.getOutputStream(); os.write(et.getText().toString().getBytes()); os.flush();  socket.shutdownOutput();

  正確案例

  場景介紹

在自定義HostnameVerifier類時,卻不實現verify方法驗證域名。

  風險分析

HostnameVerifier類中verify函數設置return true將接受任意域名,將致使惡意程序利用中間人攻擊繞過主機名校驗。

  正確措施

自定義HostnameVerifier類並實現verify方法驗證域名。

  錯誤案例

HostnameVerifier hnv = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { 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.4 證書弱校驗漏洞

  場景介紹

X509TrustManager子類中checkServerTrusted函數未校驗服務端證書的合法性。

  風險分析

應用在實現X509TrustManager時,默認覆蓋證書檢查機制方法:checkClientTrusted、checkServerTrusted和getAcceptedIssuers,未進行checkServerTrusted會致使中間人攻擊漏洞。

  正確措施

X509TrustManager子類中checkServerTrusted函數校驗服務端證書的合法性。

  錯誤案例

TrustManager tm = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 風險代碼,接受任意客戶端證書 } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 風險代碼,接受任意服務端證書 } public X509Certificate[] getAcceptedIssuers() { return null; }};sslContext.init(null, new TrustManager[] { tm }, null);

  正確案例

 public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { if (chain ==null) { throw new IllegalArgumentException(""); } for (X509Certificate certificate :chain) { certificate.checkValidity(); try { certificate.verify(serverCert.getPublicKey()); } catch (Exception e) { e.printStackTrace(); } }}


6、加解密

6.1 密鑰硬編碼風險

場景介紹

應用使用密鑰進行加密。

風險分析

硬編碼形式儲存密鑰,容易受到逆向破解攻擊。

正確措施

對密鑰進行加密處理,使用so庫進行密鑰的存儲,而且將總體的加密、解密操做都放在so庫中進行。將密鑰進行加密存儲在assets目錄下,將加密、解密過程存儲在so庫文件中,並對so庫文件進行加殼等安全保護,加強密鑰保護的安全強度。客戶端存儲須要依照業務的安全級別採用不一樣的存儲方案。

錯誤案例

public static String AESkey = "0123456789012345"; // 風險代碼,密鑰明文硬編碼在客戶端中 /**  * AES加密  */  public static String encrypt(String seed, String cleartext) throws Exception {  byte[] rawKey = getRawKey(seed.getBytes());  byte[] result = encrypt(rawKey, cleartext.getBytes());  return toHex(result);  }  /**  * AES解密  */  public static String decrypt(String seed, String encrypted) throws Exception {  byte[] rawKey = getRawKey(seed.getBytes());  byte[] enc = toByte(encrypted);  byte[] result = decrypt(rawKey, enc);  return new String(result);  } 

正確案例

KeyGenerator kg = KeyGenerator.getInstance("AES");kg.init(128, new SecureRandom(key.getBytes()));SecretKey secretKey = kg.generateKey();byte[] enCodeFormat = secretKey.getEncoded();SecretKeySpec keySpec = new SecretKeySpec(enCodeFormat, "AES");Cipher enCipher = Cipher.getInstance("AES");enCipher.init(Cipher.ENCRYPT_MODE, keySpec);byte[] result = enCipher.doFinal(content.getBytes());

6.2 使用安全隨機數

  場景介紹

應用使用隨機數生成密鑰。

  風險分析

Random生成的隨機數是被預測。

  正確措施

密鑰隨機數用安全的隨機數生成器SecureRandom

  錯誤案例

Random localRandom = new Random(System.currentTimeMillis()); // 風險代碼,使用不安全的隨機數生成器
System.out.println("# Random AES (key,plain,cipher) triples");
for (int j = 0; j < i; j++) { localRandom.nextBytes(arrayOfByte1); localRandom.nextBytes(arrayOfByte2);
AES localAES = new AES(); localAES.setKey(arrayOfByte1); byte[] arrayOfByte3 = localAES.encrypt(arrayOfByte2);
System.out.println(Util.toHEX1(arrayOfByte1) + " " + Util.toHEX1(arrayOfByte2) + " " + Util.toHEX1(arrayOfByte3));

  正確案例

private static byte[] getRawKey(byte[] seed) throws Exception {  KeyGenerator kgen = KeyGenerator.getInstance("AES");  /** * SecureRandom 實現做爲隨機源 * 返回實現指定隨機數生成器 (RNG) 算法的 SecureRandom 對象 */ SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); // 使用安全的隨機數生成器SecureRandom sr.setSeed(seed);  kgen.init(128, sr);  SecretKey skey = kgen.generateKey();  byte[] raw = skey.getEncoded();  return raw;  } 

6.3 使用自行開發私有算法風險

  場景介紹

應用須要使用加密算法。

  風險分析

使用自行開發私有算法未通過專業機構驗證,不具備安全性認證;

MD5和SHA-1已被證實不具有強抗碰撞性。

  正確措施

禁止使用MD5,SHA-1以及未經專業機構認證算法,建議使用公開成熟的加密算法以及安全的Hash算法。

  錯誤案例

public byte[] my_encrypt( byte[] plainText ) throws Exception{ byte[] cipherText = encrypt( plainText ); // 風險代碼,使用自行開發私有算法 return(cipherText);}

  正確案例

public static String Encrypt(String sSrc, String sKey) throws Exception { // 使用公開成熟的加密算法 byte[] raw = sKey.getBytes("utf-8");  SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");  Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");模式 IvParameterSpec iv = new IvParameterSpec(cKey.getBytes());iv  cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);  byte[] encrypted = cipher.doFinal(sSrc.getBytes());  return new Base64().encode(encrypted);

6.4 不安全的hash算法

  場景介紹

應用使用如MD5和SHA-1不安全的Hash算法。

  風險分析

MD5和SHA-1已被證實不具有強抗碰撞性。

  正確措施

使用SHA-256的Hash算法。

  錯誤案例

MessageDigest md = MessageDigest.getInstance("MD5"); // 風險代碼,使用不安全的HASH算法
try { md.update(toChapter1); MessageDigest tc1 = md.clone(); byte[] toChapter1Digest = tc1.digest(); md.update(toChapter2);} catch (CloneNotSupportedException cnse) { throw new DigestException("couldn't make digest of partial content");}

  正確案例

MessageDigest md = MessageDigest.getInstance("SHA-256"); // 使用安全的HASH算法
try { md.update(toChapter1); MessageDigest tc1 = md.clone(); byte[] toChapter1Digest = tc1.digest(); md.update(toChapter2);} catch (CloneNotSupportedException cnse) { throw new DigestException("couldn't make digest of partial content"); }

6.5 DES弱加密和ECB模式弱加密

  場景介紹

應用使用DES和AES對稱加密。

  風險分析

DES(密鑰默認是56位長度、算法半公開、迭代次數少)是極度不安全的;

AES的ECB加密模式容易遭到字典攻擊,安全性不夠。

  正確措施

不使用DES加密算法;

使用AES加密算法且加密模式不爲ECB模式。

  錯誤案例

public void generateKey() throws Exception{ KeyGenerator keyGen = KeyGenerator.getInstance( "DES" );  keyGen.init( 56 ); key = keyGen.generateKey();}
public byte[] des_encrypt( byte[] plainText ) throws Exception{ Cipher cipher = Cipher.getInstance( "DES/ECB/PKCS5Padding" ); // 風險代碼,使用不安全的加解密算法 cipher.init( Cipher.ENCRYPT_MODE, key ); byte[] cipherText = cipher.doFinal( plainText ); return(cipherText); }
public byte[] des_dencrypt( byte[] cipherText ) throws Exception{ Cipher cipher = Cipher.getInstance( "DES/ECB/PKCS5Padding" );// 風險代碼,使用不安全的加解密算法 cipher.init( Cipher.DECRYPT_MODE, key ); byte[] newPlainText = cipher.doFinal( cipherText ); return(newPlainText);} public static String Encrypt(String sSrc, String sKey) throws Exception { byte[] raw = sKey.getBytes("utf-8"); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // 風險代碼,使用AES加密算法中不安全的填充模式 cipher.init(Cipher.ENCRYPT_MODE, skeySpec); byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
return new Base64().encodeToString(encrypted); }
public static String Decrypt(String sSrc, String sKey) throws Exception { try { byte[] raw = sKey.getBytes("utf-8"); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// 風險代碼,使用AES加密算法中不安全的填充模式 cipher.init(Cipher.DECRYPT_MODE, skeySpec); byte[] encrypted1 = new Base64().decode(sSrc); try { byte[] original = cipher.doFinal(encrypted1); String originalString = new String(original,"utf-8"); return originalString; } catch (Exception e) { System.out.println(e.toString()); return null; } } catch (Exception ex) { System.out.println(ex.toString()); return null; } }

  正確案例

public static String Encrypt(String sSrc, String sKey) throws Exception {  byte[] raw = sKey.getBytes("utf-8");  SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");  Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");// AES加密算法,且不是ECB加密模式 IvParameterSpec iv = new IvParameterSpec(cKey.getBytes());//AES加密算法CBC模式須要一個向量iv  cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);  byte[] encrypted = cipher.doFinal(sSrc.getBytes());  return new Base64().encode(encrypted);  } 

6.6 RSA不安全的密鑰長度

  場景介紹

應用使用RSA非對稱加密。

  風險分析

在使用RSA加密時,當密鑰小於2048bit時,很容易被破解並計算出密鑰。

  正確措施

使用RSA加密時,密鑰長度爲2048位。

  錯誤案例

public static KeyPair getRSAKey() throws NoSuchAlgorithmException{ KeyPairGenerator keyGen = KeyPairGenerator.getInstance( "RSA" ); keyGen.initialize( 512 ); // 風險代碼,密鑰長度不足 KeyPair key = keyGen.generateKeyPair(); return(key);}

  正確案例

public static KeyPair getRSAKey() throws NoSuchAlgorithmException{ KeyPairGenerator keyGen = KeyPairGenerator.getInstance( "RSA" ); keyGen.initialize( 2048 ); // 正確代碼,密鑰長度達到安全級別 KeyPair key = keyGen.generateKeyPair(); return(key);}

6.7 IVParameterSPec不安全初始化

  場景介紹

使用固定初始化向量,會致使密碼文本可預測性提升,容易受到字典式攻擊。iv的做用主要是用於產生密文的第一個block,以使最終生成的密文產生差別(明文相同的狀況下),使密碼攻擊變得更爲困難,除此以外iv並沒有其餘用途。所以iv經過隨機方式產生是一種十分簡便、有效的途徑。

  風險分析

CBC模式以上時,若是偏移量固定,則密碼文本可預測性會高得多。

  正確措施

IVParameterSpec初始化時,不使用常量vector。

  錯誤案例

IvParameterSpec iv_ = new IvParameterSpec(「1234567890」.getBytes());//風險代碼,使用常量vector

  正確案例

Byte[] rand = new byte[16];SecureRandom r = new SecureRandom();r.nextBytes(rand);IvParameterSpec iv = new IvParameterSpec(rand);

6.8 RSA中不使用Padding風險

  場景介紹

使用RSA公鑰時一般會綁定一個padding,是爲了防止一些依賴於no padding對RSA算法的攻擊。

  風險分析

RSA選擇RSA_NO_PADDING填充模式時,若是明文字節不夠,加密的時候會在你的明文前面,前向的填充零,解密後的明文也會包括前面填充的零,更容易被破解。

  正確措施

使用Padding模式。

  錯誤案例

...Cipher rsa = null;try {rsa = javax.crypto.Cipher.getInstance("RSA/NONE/NoPadding");} //風險代碼,未設置padding catch (java.security.NoSuchAlgorithmException e) {} catch (javax.crypto.NoSuchPaddingException e) {}
SecretKeySpec key = new SecretKeySpec(rawKeyData, "RSA");Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding"); //風險代碼,未設置paddingcipher.init(Cipher.DECRYPT_MODE, key);...

  正確案例

Cipher cipher = Cipher.getInstance("RSA/CBC/PCKS1", "BC");cipher.init(Cipher.DECRYPT_MODE, privateKey);cipher.doFinal(miwen);


7、日誌審計

7.1 日誌泄露

場景介紹

一般應用在運行時會將一些運行過程的信息寫到日誌記錄中。若是日誌中記錄了敏感信息,而這樣的日誌信息在客戶端是不安全,容易致使這些敏感信息泄露。

風險分析

在應用的開發過程當中,爲了方便調試,一般會使用log函數輸出一些關鍵流程的信息,這些信息中一般會包含敏感內容,如執行流程、明文的用戶名密碼、內核地址信息等。

正確措施

新建寫一個日誌類,以取代android.util.Log類,經過DEBUG控制日誌輸出或在proguard.cfg中移除日誌;

調試過程當中禁止打印內核的地址和其餘重要信息。

錯誤案例

//smali中包含如下內容Landroid/util/Log;->d(Landroid/util/Log;->v(Landroid/util/Log;->i(Landroid/util/Log;->e(Landroid/util/Log;->w(

正確案例

//方法一 新建寫一個日誌類public class LogUtil { public static final boolean DEBUG = true; public static void v(String tag,String msg) { if (DEBUG) { Log.v(tag, msg); } } public static void d(String tag,String msg) { if (DEBUG) { Log.d(tag, msg); } } public static void i(String tag,String msg) { Log.i(tag, msg); } public static void w(String tag,String msg) { if (DEBUG) { Log.w(tag, msg); } } public static void e(String tag,String msg) { if (DEBUG) { Log.e(tag, msg); } } }
//方法二//在proguard.cfg文件中加入-assumenosideeffects class android.util.Log { public static *** d(...);
public static *** e(...);
public static *** i(...);
public static *** v(...);
public static *** w(...);
}-assumenosideeffects class java.lang.Exception{ public *** printStackTrace(...); }


8、webview安全使用

8.1 WebView 密碼明文存儲密碼

場景介紹

Android的Webview組件默認打開提示用戶是否保存密碼的功能,當用戶選擇保存,用戶名和密碼將被明文存儲在該應用目錄databases/webview.db中。當其餘惡意程序經過提權或者root的方式訪問該應用的Webview數據庫,從而可竊取登陸信息以及密碼。

風險分析

Webview組件中未設置關閉自動保存密碼功能,用戶名和密碼被明文存儲。

  正確措施

經過設置Webview.getSettings.setSavePassword(flase)來關閉Webview組件的保存密碼功能。

  錯誤案例

WebSettings settings = mWebView.getSettings();mWebView.requestFocusFromTouch();settings.setPluginState(WebSettings.PluginState.ON);settings.setUseWideViewPort(true);//未修改默認保存密碼功能

正確案例

WebSettings settings = mWebView.getSettings();mWebView.requestFocusFromTouch();settings.setPluginState(WebSettings.PluginState.ON);settings.setUseWideViewPort(true);//設置默認保存密碼功能settings.setSavePassword(false)

8.2 WebView域控制不嚴格

場景介紹

WebView若是打開了對JavaScript的支持,同時未對file://形式的URL作限制。

風險分析

經過 javascript的延時執行和將當前文件替換成指向其它文件的軟連接就能夠讀取到被符號連接所指的文件,可在無特殊權限下盜取應用的任意私有文件,尤爲是瀏覽器,會致使coookie、私有文件、數據庫等敏感信息泄露。

  正確措施

setAllowFileAccess加載JavaScript時對file://形式的URL作限制禁止setAllowFileAccessFromFileURLs、setAllowUniversalAccessFromFileURLsandroid 4.1 及之後的版本中這兩項設置默認是禁止)

  錯誤案例

webView = (WebView) findViewById(R.id.webView);webView.getSettings().setAllowFileAccess(true); webView.getSettings().setAllowFileAccessFromFileURLs(true); webView.getSettings().setAllowUniversalAccessFromFileURLs(true); Intent i = getIntent();String url = i.getData().toString(); //url = file:///data/local/tmp/attack.html webView.loadUrl(url);

  正確案例

1、對於不須要使用 file 協議的應用,禁用 file 協議setAllowFileAccess(false); setAllowFileAccessFromFileURLs(false);setAllowUniversalAccessFromFileURLs(false);2、對於須要使用file協議的應用,禁止file協議調用javascriptsetAllowFileAccess(true); setAllowFileAccessFromFileURLs(false);setAllowUniversalAccessFromFileURLs(false);if (url.startsWith("file://") { setJavaScriptEnabled(false);} else { setJavaScriptEnabled(true);}

8.3 WebView不校驗證書

場景介紹

Android WebView組件加載網頁發生證書認證錯誤時,會調用WebViewClient類的onReceivedSslError方法,調用了handler.proceed()來忽略該證書錯誤。

  風險分析

使用handler.proceed()忽略全部SSL證書驗證錯誤,使應用容易受到中間人攻擊。攻擊者可能會更改受影響的 WebView 內容、讀取傳輸的數據(例如登陸憑據),以及執行應用中使用JavaScript的代碼。

  正確措施

當發生證書認證錯誤時,採用默認的處理方法SslErrorHandler.cancel(),中止加載問題頁面。

  錯誤案例

webview.setWebViewClient(new WebViewClient() { @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); }}

正確案例

webView.setWebViewClient(new WebViewClient() { @Overridepublic void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {if (error.getPrimaryError() == SslError.SSL_INVALID) { // 若是手動校驗證書SHA256成功,容許加載頁面 if(SSLCertUtil.isSSLCertOk(error.getCertificate(), "sha256值")) { handler.proceed(); } else { try { new AlertDialog.Builder(MainActivity.this) .setTitle("Warning") .setMessage("Certificate verification failed") .setPositiveButton("Quit", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { System.exit(0); dialog.dismiss(); } }).show(); } catch (Exception e) { e.printStackTrace(); } } } else { handler.cancel(); } }});


9、其它

9.1 Zip文件目錄遍歷

場景介紹

應用使用unzip解壓文件。

  風險分析

Zip壓縮包中,容許文件名存在"../"字符串,可利用多個「../」在解壓時改變ZIP包中某個文件的存放位置,覆蓋掉應用原有的文件。

  正確措施

對重要的ZIP壓縮包文件進行數字簽名校驗,校驗經過才進行解壓。 

在調用getName()方法以後,判斷路徑中是否有"../"字符串,若是有作相應的處理。

  錯誤案例

File srcFile = new File(srcFilePath);FileInputStream fileInputStream = new FileInputStream(srcFile);ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(fileInputStream));ZipEntry zipEntry = null;while ((zipEntry = zipInputStream.getNextEntry()) != null){ String entryName = zipEntry.getName(); Log.d(TAG, "entry name:" + entryName);}

  正確案例

String entryName = file.getName();if(entryName.contains("../.")){ throw new Exception("unsecurity zipFile!");}

9.2 AndroidManifest文件Debuggable配置

場景介紹

android:debuggable這個標識用來代表該應用是否能夠被調試,默認值爲 false。可是在開發應用的測試版本經常須要進行調試,將debuggable設置爲true。

風險分析

AndroidManifest.xml中定義Debuggable項,若是該項被打開,應用存在被調試的風險。

正確措施

發佈版本時,應顯示關閉調試屬性,將AndroidManifest.xml中application的debuggable屬性顯示設置爲false。

錯誤案例

<application android:debuggable="true"</application>

正確案例

<application android:debuggable="false"</application>





本文分享自微信公衆號 - OPPO互聯網技術(OPPO_tech)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索