百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Android代码安全开发指导 安卓开发常用代码

bigegpt 2024-10-12 05:15 9 浏览

私信我或关注微信号:猿来如此呀,回复:学习,获取免费学习资源包。

随着移动通信技术的发展,移动终端不断朝向智能化方向迈进。手机移动终端作为移动互联网时代最主要的载体,其安全性正面临严峻的挑战。本Android代码安全开发指导规范,旨在最大程度减少安全隐患,并统一规范软件开发在功能实现、编码阶段的行为。


一、适用范围

本规范规定了Android平台软件开发的编码设计与实现。

二、组件安全

Android组件安全此处共涉及如下三方面风险:

2.1 导出组件拒绝服务风险

场景介绍

应用在AndroidManifest.xml文件中设置了Activity/Service/Broadcast/Provider为导出,导致该组件可以被第三方程序调用,并可以通过Intent接受参数传入。如果这些组件在从Intent获取参数的时候没有对其合法性进行校验,且代码没有使用异常处理,则会导致应用抛出异常无法被捕获,进而导致应用崩溃。第三方恶意程序可以通过在后台不断发送能够使应用崩溃的Intent,使得程序无法正常运行。

风险分析

组件没有进行权限管控,被外部非法访问;

组件没有对Intent参数进行校验或异常处理,导致应用崩溃。

正确措施

确认组件是否处于外部可调用状态;判断组件是否有必要公开,如无必要,则必须设置为非导出状态(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.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.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.1 HTTPS连接检测

场景介绍

Http没有使用TLS1.2以上安全协议。

风险分析

http存在的风险:

数据被监听窃取:通过软件抓包,可以看到请求的内容。

数据被篡改: 请求的数据被修改。

中间人重放攻击:数据被中间人监听到,再次发送给服务器。

正确措施

应用通过HTTPS访问服务器。

错误案例


// HttpURLConnection
URL 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();
// OKHttp
OkHttpClient 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);
}
// OKHttp
Uri 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.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"); //风险代码,未设置padding
cipher.init(Cipher.DECRYPT_MODE, key);...

正确案例


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


七、日志审计

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(...);
 
}


八、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、setAllowUniversalAccessFromFileURLs(android 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协议调用javascript
setAllowFileAccess(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() {
 @Override
public 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.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>

来源网络侵权联系删除

私信我或关注微信号:猿来如此呀,回复:学习,获取免费学习资源包

相关推荐

最全的MySQL总结,助你向阿里“开炮”(面试题+笔记+思维图)

前言作为一名编程人员,对MySQL一定不会陌生,尤其是互联网行业,对MySQL的使用是比较多的。对于求职者来说,MySQL又是面试中一定会问到的重点,很多人拥有大厂梦,却因为MySQL败下阵来。实际上...

Redis数据库从入门到精通(redis数据库设计)

目录一、常见的非关系型数据库NOSQL分类二、了解Redis三、Redis的单节点安装教程四、Redis的常用命令1、Help帮助命令2、SET命令3、过期命令4、查找键命令5、操作键命令6、GET命...

netcore 急速接入第三方登录,不看后悔

新年新气象,趁着新年的喜庆,肝了十来天,终于发了第一版,希望大家喜欢。如果有不喜欢看文字的童鞋,可以直接看下面的地址体验一下:https://oauthlogin.net/前言此次带来得这个小项目是...

精选 30 个 C++ 面试题(含解析)(c++面试题和答案汇总)

大家好,我是柠檬哥,专注编程知识分享。欢迎关注@程序员柠檬橙,编程路上不迷路,私信发送以下关键字获取编程资源:发送1024打包下载10个G编程资源学习资料发送001获取阿里大神LeetCode...

Oracle 12c系列(一)|多租户容器数据库

作者杨禹航出品沃趣技术Oracle12.1发布至今已有多年,但国内Oracle12C的用户并不多,随着12.2在去年的发布,选择安装Oracle12c的客户量明显增加,在接下来的几年中,Or...

flutter系列之:UI layout简介(flutter-ui-nice)

简介对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做layout,就是用来描述如何将组件进行摆放的一个约束。在flutter中,基本上所有的对象都是wi...

Flutter 分页功能表格控件(flutter 列表)

老孟导读:前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析来来。PaginatedDataTablePaginatedDataTable是一个带分页功能的DataTable,...

Flutter | 使用BottomNavigationBar快速构建底部导航

平时我们在使用app时经常会看到底部导航栏,而在flutter中它的实现也较为简单.需要用到的组件:BottomNavigationBar导航栏的主体BottomNavigationBarI...

Android中的数据库和本地存储在Flutter中是怎样实现的

如何使用SharedPreferences?在Android中,你可以使用SharedPreferencesAPI来存储少量的键值对。在Flutter中,使用Shared_Pref...

Flet,一个Flutter应用的实用Python库!

▼Flet:用Python轻松构建跨平台应用!在纷繁复杂的Python框架中,Flet宛如一缕清风,为开发者带来极致的跨平台应用开发体验。它用最简单的Python代码,帮你实现移动端、桌面端...

flutter系列之:做一个图像滤镜(flutter photo)

简介很多时候,我们需要一些特效功能,比如给图片做个滤镜什么的,如果是h5页面,那么我们可以很容易的通过css滤镜来实现这个功能。那么如果在flutter中,如果要实现这样的滤镜功能应该怎么处理呢?一起...

flutter软件开发笔记20-flutter web开发

flutterweb开发优势比较多,采用统一的语言,就能开发不同类型的软件,在web开发中,特别是后台式软件中,相比传统的html5开发,更高效,有点像c++编程的方式,把web设计出来了。一...

Flutter实战-请求封装(五)之设置抓包Proxy

用了两年的flutter,有了一些心得,不虚头巴脑,只求实战有用,以供学习或使用flutter的小伙伴参考,学习尚浅,如有不正确的地方还望各路大神指正,以免误人子弟,在此拜谢~(原创不易,转发请标注来...

为什么不在 Flutter 中使用全局变量来管理状态

我相信没有人用全局变量来管理Flutter应用程序的状态。毫无疑问,我们的Flutter应用程序需要状态管理包或Flutter的基本小部件(例如InheritedWidget或St...

Flutter 攻略(Dart基本数据类型,变量 整理 2)

代码运行从main方法开始voidmain(){print("hellodart");}变量与常量var声明变量未初始化变量为nullvarc;//未初始化print(c)...