Danh mục OWASP: MASVS-CODE: Chất lượng mã
Tổng quan
Việc tải mã một cách linh động vào ứng dụng sẽ làm tăng mức độ rủi ro mà bạn phải giảm thiểu. Kẻ tấn công có thể can thiệp hoặc thay thế mã để truy cập vào dữ liệu nhạy cảm hoặc thực hiện các hành động gây hại.
Nhiều hình thức tải mã động, đặc biệt là những hình thức sử dụng các nguồn từ xa, vi phạm chính sách của Google Play và có thể dẫn đến việc ứng dụng của bạn bị tạm ngưng trên Google Play.
Tác động
Nếu kẻ tấn công tìm cách giành được quyền truy cập vào mã sẽ được tải vào ứng dụng, thì chúng có thể sửa đổi mã đó để hỗ trợ mục tiêu của mình. Điều này có thể dẫn đến hành vi đánh cắp dữ liệu và khai thác thực thi mã. Ngay cả khi không thể sửa đổi mã để thực hiện các hành động tuỳ ý, kẻ tấn công vẫn có thể làm hỏng hoặc xoá mã, từ đó ảnh hưởng đến khả năng hoạt động của ứng dụng.
Giải pháp giảm thiểu
Tránh sử dụng tính năng tải mã động
Trừ phi có nhu cầu công việc, hãy tránh tải mã động. Bạn nên đưa tất cả chức năng trực tiếp vào ứng dụng bất cứ khi nào có thể.
Sử dụng nguồn đáng tin cậy
Mã sẽ được tải vào ứng dụng phải được lưu trữ ở các vị trí đáng tin cậy. Đối với bộ nhớ cục bộ, bạn nên sử dụng bộ nhớ trong của ứng dụng hoặc bộ nhớ có giới hạn (dành cho Android 10 trở lên). Những vị trí này có các biện pháp để tránh truy cập trực tiếp từ các ứng dụng và người dùng khác.
Khi tải mã từ các vị trí từ xa như URL, hãy tránh sử dụng bên thứ ba khi có thể và lưu trữ mã trong cơ sở hạ tầng của riêng bạn, tuân theo các phương pháp hay nhất về bảo mật. Nếu bạn cần tải mã của bên thứ ba, hãy đảm bảo rằng nhà cung cấp là một nhà cung cấp đáng tin cậy.
Kiểm tra tính toàn vẹn
Bạn nên kiểm tra tính toàn vẹn để đảm bảo rằng mã không bị sửa đổi. Bạn nên thực hiện các bước kiểm tra này trước khi tải mã vào ứng dụng.
Khi tải tài nguyên từ xa, bạn có thể sử dụng tính toàn vẹn của tài nguyên phụ để xác thực tính toàn vẹn của tài nguyên đã truy cập.
Khi tải tài nguyên từ bộ nhớ ngoài, hãy sử dụng tính năng kiểm tra tính toàn vẹn để xác minh rằng không có ứng dụng nào khác can thiệp vào dữ liệu hoặc mã này. Giá trị băm của các tệp phải được lưu trữ một cách an toàn, tốt nhất là mã hoá và lưu vào bộ nhớ trong.
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!");
}
}
}
Ký mã
Một cách khác để đảm bảo tính toàn vẹn của dữ liệu là ký mã và xác minh chữ ký của mã trước khi tải mã. Phương thức này có ưu điểm là cũng đảm bảo tính toàn vẹn của mã băm, không chỉ mã đó, mà còn cung cấp thêm một lớp bảo vệ chống can thiệp.
Mặc dù việc ký mã cung cấp thêm các lớp bảo mật, nhưng điều quan trọng là bạn phải lưu ý rằng đây là một quy trình phức tạp hơn và có thể cần thêm nỗ lực và tài nguyên để triển khai thành công.
Bạn có thể tìm thấy một số ví dụ về việc ký mã trong phần Tài nguyên của tài liệu này.
Tài nguyên
- Tính toàn vẹn của tài nguyên phụ
- Ký dữ liệu bằng chữ ký số
- Ký mã
- Dữ liệu nhạy cảm được lưu trữ trong bộ nhớ ngoài