OWASP 类别: MASVS-CODE: 代码质量
概述
将代码动态加载到应用程序中会引入必须减轻的风险级别。攻击者可能会篡改或替换代码以访问敏感数据或执行有害操作。
许多动态代码加载形式,尤其是那些使用远程源的代码加载形式,违反 Google Play 政策,并可能导致您的应用程序从 Google Play 中暂停。
影响
如果攻击者设法访问将加载到应用程序中的代码,他们可能会修改它以支持其目标。这会导致数据泄露和代码执行漏洞。即使攻击者无法修改代码以执行他们选择的任意操作,他们仍然可能破坏或删除代码,从而影响应用程序的可用性。
缓解措施
避免使用动态代码加载
除非有业务需求,否则请避免使用动态代码加载。您应尽可能将所有功能直接包含在应用程序中。
使用受信任的源
将加载到应用程序中的代码应存储在受信任的位置。对于本地存储,应用程序内部存储或作用域存储(对于 Android 10 及更高版本)是推荐的位置。这些位置具有措施来防止其他应用程序和用户直接访问。
从远程位置(如 URL)加载代码时,尽可能避免使用第三方,并将代码存储在您自己的基础设施中,遵循安全最佳实践。如果您需要加载第三方代码,请确保提供者是受信任的。
执行完整性检查
建议执行完整性检查,以确保代码未被篡改。应在将代码加载到应用程序之前执行这些检查。
在加载远程资源时,可以使用子资源完整性来验证所访问资源的完整性。
在从外部存储加载资源时,使用完整性检查以验证没有其他应用程序篡改了此数据或代码。文件的哈希值应以安全的方式存储,最好是加密并存储在内部存储中。
Kotlin
package com.example.myapplication
import java.io.BufferedInputStream
import java.io.FileInputStream
import java.io.IOException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
object FileIntegrityChecker {
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun getIntegrityHash(filePath: String?): String {
val md = MessageDigest.getInstance("SHA-256") // You can choose other algorithms as needed
val buffer = ByteArray(8192)
var bytesRead: Int
BufferedInputStream(FileInputStream(filePath)).use { fis ->
while (fis.read(buffer).also { bytesRead = it } != -1) {
md.update(buffer, 0, bytesRead)
}
}
private fun bytesToHex(bytes: ByteArray): String {
val sb = StringBuilder(bytes.length * 2)
for (b in bytes) {
sb.append(String.format("%02x", b))
}
return sb.toString()
}
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun verifyIntegrity(filePath: String?, expectedHash: String): Boolean {
val actualHash = getIntegrityHash(filePath)
return actualHash == expectedHash
}
@Throws(Exception::class)
@JvmStatic
fun main(args: Array<String>) {
val filePath = "/path/to/your/file"
val expectedHash = "your_expected_hash_value"
if (verifyIntegrity(filePath, expectedHash)) {
println("File integrity is valid!")
} else {
println("File integrity is compromised!")
}
}
}
Java
package com.example.myapplication;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class FileIntegrityChecker {
public static String getIntegrityHash(String filePath) throws IOException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256"); // You can choose other algorithms as needed
byte[] buffer = new byte[8192];
int bytesRead;
try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream(filePath))) {
while ((bytesRead = fis.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead);
}
}
byte[] digest = md.digest();
return bytesToHex(digest);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static boolean verifyIntegrity(String filePath, String expectedHash) throws IOException, NoSuchAlgorithmException {
String actualHash = getIntegrityHash(filePath);
return actualHash.equals(expectedHash);
}
public static void main(String[] args) throws Exception {
String filePath = "/path/to/your/file";
String expectedHash = "your_expected_hash_value";
if (verifyIntegrity(filePath, expectedHash)) {
System.out.println("File integrity is valid!");
} else {
System.out.println("File integrity is compromised!");
}
}
}
对代码进行签名
确保数据完整性的另一种方法是对代码进行签名,并在加载代码之前验证其签名。这种方法的优势还在于,它不仅可以确保代码本身的完整性,还可以确保哈希代码的完整性,从而提供额外的防篡改保护。
尽管代码签名提供了额外的安全层,但需要注意的是,它是一个更复杂的过程,可能需要额外的努力和资源才能成功实施。
在本文档的“资源”部分中可以找到一些代码签名的示例。