Android代码安全开发指导 安卓开发常用代码
bigegpt 2024-10-12 05:15 6 浏览
私信我或关注微信号:猿来如此呀,回复:学习,获取免费学习资源包。
随着移动通信技术的发展,移动终端不断朝向智能化方向迈进。手机移动终端作为移动互联网时代最主要的载体,其安全性正面临严峻的挑战。本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>
来源网络侵权联系删除
私信我或关注微信号:猿来如此呀,回复:学习,获取免费学习资源包
相关推荐
- 得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践
-
一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...
- warm-flow新春版:网关直连和流程图重构
-
本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...
- 扣子空间体验报告
-
在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...
- spider-flow:开源的可视化方式定义爬虫方案
-
spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...
- solon-flow 你好世界!
-
solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...
- 新一代开源爬虫平台:SpiderFlow
-
SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...
- 通过 SQL 训练机器学习模型的引擎
-
关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...
- 鼠须管输入法rime for Mac
-
鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...
- Go语言 1.20 版本正式发布:新版详细介绍
-
Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...
- iOS 10平台SpriteKit新特性之Tile Maps(上)
-
简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...
- 程序员简历例句—范例Java、Python、C++模板
-
个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...
- Telerik UI for iOS Q3 2015正式发布
-
近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...
- ios使用ijkplayer+nginx进行视频直播
-
上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...
- IOS技术分享|iOS快速生成开发文档(一)
-
前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...
- macOS下配置VS Code C++开发环境
-
本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- httperror403.14-forbidden (63)
- logstashinput (65)
- hadoop端口 (65)
- dockernetworkconnect (63)
- esxi7 (63)
- vue阻止冒泡 (67)
- c#for循环 (63)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- java大写转小写 (63)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)