Tiết lộ thông tin nhật ký

Danh mục OWASP: MASVS-STORAGE: Bộ nhớ

Tổng quan

Tiết lộ thông tin nhật ký là một loại lỗ hổng bảo mật mà ứng dụng ghi dữ liệu nhạy cảm vào nhật ký thiết bị. Nếu rơi vào tay những kẻ thực hiện hành vi ác ý, thì thông tin nhạy cảm như vậy có thể có giá trị ngay tức thì – chẳng hạn như thông tin xác thực hay thông tin nhận dạng cá nhân (PII) của người dùng – hoặc có thể tạo điều kiện cho các cuộc tấn công khác.

Vấn đề này có thể xảy ra trong những trường hợp sau đây:

  • Nhật ký do ứng dụng tạo:
    • Nhật ký cấp quyền truy cập có chủ đích cho các đối tượng không được phép, nhưng nhật ký vô tình chứa dữ liệu nhạy cảm.
    • Nhật ký chứa dữ liệu nhạy cảm có chủ đích nhưng các đối tượng không được phép có thể tình cờ truy cập được vào nhật ký.
    • Nhật ký lỗi chung có thể đôi khi ghi dữ liệu nhạy cảm, tuỳ thuộc vào thông báo lỗi được kích hoạt.
  • Nhật ký được tạo bên ngoài:
    • Các thành phần bên ngoài chịu trách nhiệm ghi nhật ký chứa dữ liệu nhạy cảm.

Các câu lệnh Log.* của Android sẽ ghi vào vùng đệm bộ nhớ chung logcat. Đối với Android 4.1 (API cấp 16) trở lên, quyền truy cập để đọc logcat sẽ chỉ được cấp cho các ứng dụng đặc quyền của hệ thống bằng cách khai báo quyền READ_LOGS. Tuy nhiên, Android hỗ trợ rất nhiều thiết bị mà trong đó các ứng dụng tải sẵn đôi khi khai báo đặc quyền READ_LOGS. Do vậy, bạn không nên ghi nhật ký trực tiếp vào logcat vì việc đó khiến dữ liệu dễ bị rò rỉ hơn.

Đảm bảo toàn bộ dữ liệu nhật ký trong logcat được dọn dẹp trong các phiên bản không gỡ lỗi của ứng dụng. Xoá mọi dữ liệu có thể mang tính chất nhạy cảm. Để phòng ngừa thêm, hãy sử dụng các công cụ như R8 để xoá mọi cấp độ nhật ký, ngoại trừ các cảnh báo và lỗi. Nếu bạn cần nhật ký chi tiết hơn, hãy sử dụng bộ nhớ trong và quản lý trực tiếp nhật ký của riêng mình, thay vì sử dụng nhật ký hệ thống.

Tác động

Mức độ nghiêm trọng của lớp lỗ hổng bảo mật Tiết lộ thông tin nhật ký có thể khác nhau, tuỳ thuộc vào ngữ cảnh và loại dữ liệu nhạy cảm. Nhìn chung, lớp lỗ hổng bảo mật này gây tổn hại đến tính bảo mật của những thông tin quan trọng như PII (Thông tin nhận dạng cá nhân) và thông tin xác thực

Giải pháp giảm thiểu

Giải pháp chung

Như một biện pháp giành quyền chung trong quá trình thiết kế và triển khai, hãy xác định ranh giới tin cậy theo nguyên tắc về đặc quyền tối thiểu. Tốt nhất là dữ liệu nhạy cảm không nên vượt qua hoặc chạm đến giới hạn của mọi vùng tin cậy. Điều này giúp củng cố việc phân tách đặc quyền.

Đừng ghi nhật ký dữ liệu nhạy cảm. Chỉ ghi nhật ký hằng số thời gian biên dịch bất cứ khi nào có thể. Bạn có thể sử dụng công cụ ErrorProne để chú giải hằng số thời gian biên dịch.

Tránh nhật ký ghi các câu lệnh có thể chứa thông tin không mong muốn, bao gồm cả dữ liệu nhạy cảm, tuỳ thuộc vào lỗi được kích hoạt. Dữ liệu ghi trong nhật ký và nhật ký lỗi luôn luôn chỉ nên bao gồm thông tin có thể dự đoán.

Tránh ghi nhật ký vào logcat. Lý do là vì việc ghi nhật ký vào logcat có thể trở thành vấn đề về quyền riêng tư đối với các ứng dụng có quyền READ_LOGS. Cách làm như vậy cũng không hiệu quả vì không thể kích hoạt cảnh báo hoặc không truy vấn được. Ứng dụng chỉ nên định cấu hình phần phụ trợ logcat cho các bản dựng của nhà phát triển.

Hầu hết các thư viện quản lý nhật ký cho phép xác định các cấp độ nhật ký, từ đó cho phép ghi lại lượng thông tin khác nhau giữa nhật ký gỡ lỗi và nhật ký thực tế. Thay đổi cấp độ nhật ký để khác với "gỡ lỗi" ngay khi quá trình kiểm thử sản phẩm kết thúc.

Xoá tối đa các cấp độ nhật ký khỏi phiên bản chính thức. Nếu bạn không thể tránh giữ nhật ký trong phiên bản chính thức, hãy xoá biến không cố định khỏi câu lệnh nhật ký. Các trường hợp sau có thể xảy ra:

  • Bạn có thể xoá mọi nhật ký khỏi Phiên bản chính thức.
  • Bạn cần lưu giữ nhật ký Cảnh báo và lỗi trong Phiên bản chính thức.

Đối với cả hai trường hợp trên, hãy xoá nhật ký theo cách tự động bằng các thư viện như R8. Việc cố gắng xoá nhật ký theo cách thủ công sẽ dễ gặp lỗi. Trong quá trình tối ưu hoá mã, bạn có thể thiết lập R8 để xoá an toàn các cấp độ nhật ký mà bạn muốn giữ lại để gỡ lỗi, nhưng sẽ xoá trong Phiên bản chính thức.

Nếu bạn định ghi nhật ký vào Phiên bản chính thức, hãy chuẩn bị các cờ mà bạn có thể sử dụng để tắt tính năng ghi nhật ký có điều kiện trong trường hợp xảy ra sự cố. Cờ Ứng phó với sự cố nên ưu tiên những tiêu chí sau: triển khai an toàn, triển khai nhanh chóng và dễ dàng, khả năng ẩn hoàn toàn nhật ký, mức sử dụng bộ nhớ cũng như chi phí hiệu suất của việc quét từng thông điệp nhật ký.

Xoá nhật ký trong logcat từ bản dựng Chính thức bằng R8.

Trong Android Studio 3.4 hoặc trình bổ trợ Android cho Gradle 3.4.0 trở lên, R8 là trình biên dịch mặc định để tối ưu hoá và rút gọn mã. Tuy nhiên, bạn cần phải bật R8.

R8 đã thay thế ProGuard, nhưng tệp quy tắc trong thư mục gốc của dự án vẫn được gọi là proguard-rules.pro. Đoạn mã sau đây cho thấy một tệp proguard-rules.pro mẫu giúp xoá mọi nhật ký khỏi phiên bản chính thức, ngoại trừ cảnh báo và lỗi:

-assumenosideeffects class android.util.Log {
    private static final String TAG = "MyTAG";
    public static boolean isLoggable(java.lang.String, int);
    public static int v(TAG, "My log as verbose");
    public static int d(TAG, "My log as debug");
    public static int i(TAG, "My log as information");
}

Tệp proguard-rules.pro mẫu sau đây sẽ xoá mọi nhật ký khỏi phiên bản chính thức:

-assumenosideeffects class android.util.Log {
    private static final String TAG = "MyTAG";
    public static boolean isLoggable(java.lang.String, int);
    public static int v(TAG, "My log as verbose");
    public static int d(TAG, "My log as debug");
    public static int i(TAG, "My log as information");
    public static int w(TAG, "My log as warning");
    public static int e(TAG, "My log as error");
}

Lưu ý rằng R8 cung cấp chức năng rút gọn ứng dụng và chức năng xoá nhật ký. Nếu bạn chỉ muốn sử dụng R8 cho chức năng loại bỏ nhật ký, hãy thêm phần sau vào tệp proguard-rules.pro:

-dontwarn **
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose

-optimizations !code/simplification/arithmetic,!code/allocation/variable
-keep class **
-keepclassmembers class *{*;}
-keepattributes *

Dọn dẹp mọi nhật ký cuối cùng trong phiên bản chính thức có chứa dữ liệu nhạy cảm

Để tránh làm rò rỉ dữ liệu nhạy cảm, hãy đảm bảo dọn dẹp mọi dữ liệu nhật ký trong logcat trên các phiên bản không gỡ lỗi của ứng dụng. Xoá mọi dữ liệu có thể mang tính chất nhạy cảm.

Ví dụ:

Kotlin

data class Credential<T>(val data: String) {
  /** Returns a redacted value to avoid accidental inclusion in logs. */
  override fun toString() = "Credential XX"
}

fun checkNoMatches(list: List<Any>) {
    if (!list.isEmpty()) {
          Log.e(TAG, "Expected empty list, but was %s", list)
    }
}

Java

public class Credential<T> {
  private T t;
  /** Returns a redacted value to avoid accidental inclusion in logs. */
  public String toString(){
         return "Credential XX";
  }
}

private void checkNoMatches(List<E> list) {
   if (!list.isEmpty()) {
          Log.e(TAG, "Expected empty list, but was %s", list);
   }
}

Ẩn dữ liệu nhạy cảm trong nhật ký

Nếu bạn phải đưa dữ liệu nhạy cảm vào nhật ký, bạn nên dọn dẹp nhật ký trước khi ghi nhật ký để xoá hoặc làm rối mã nguồn dữ liệu nhạy cảm. Để làm như vậy, hãy sử dụng một trong các kỹ thuật sau:

  • Tokenization (Mã hoá kỹ thuật số). Nếu dữ liệu nhạy cảm được lưu trữ trong kho lưu trữ, chẳng hạn như hệ thống quản lý mã hoá (chứa thông tin bí mật có thể được tham chiếu bằng mã thông báo), hãy ghi nhật ký mã thông báo thay vì ghi dữ liệu nhạy cảm.
  • Che giấu dữ liệu. Che giấu dữ liệu là quá trình một chiều không thể đảo ngược. Đây là kỹ thuật tạo phiên bản dữ liệu nhạy cảm có cấu trúc tương tự như dữ liệu gốc nhưng ẩn thông tin nhạy cảm nhất có trong một trường. Ví dụ: Thay thế số thẻ tín dụng 1234-5678-9012-3456 bằng XXXX-XXXX-XXXX-1313. Trước khi phát hành phiên bản chính thức của ứng dụng, bạn nên hoàn tất quy trình đánh giá khả năng bảo mật để xem xét kỹ lưỡng việc sử dụng tính năng che giấu dữ liệu. Cảnh báo: Đừng sử dụng tính năng che giấu dữ liệu trong những trường hợp mà ngay cả việc chỉ tiết lộ một phần dữ liệu nhạy cảm cũng có thể ảnh hưởng đáng kể đến tính bảo mật, chẳng hạn như khi xử lý mật khẩu.
  • Ẩn. Kỹ thuật ẩn cũng tương tự như kỹ thuật che giấu, nhưng tất cả thông tin có trong một trường sẽ bị ẩn. Ví dụ: Thay thế số thẻ tín dụng 1234-5678-9012-3456 bằng XXXX-XXXX-XXXX-XXXX.
  • Lọc. Triển khai các chuỗi định dạng trong thư viện ghi nhật ký mà bạn chọn nếu các chuỗi định dạng đó chưa tồn tại, để hỗ trợ việc sửa đổi các giá trị không cố định trong câu lệnh nhật ký.

Bạn chỉ nên thực hiện ghi nhật ký thông qua thành phần "trình dọn dẹp nhật ký" để đảm bảo mọi dữ liệu nhật ký đều được dọn dẹp trước khi ghi, như minh hoạ trong đoạn mã sau.

Kotlin

data class ToMask<T>(private val data: T) {
  // Prevents accidental logging when an error is encountered.
  override fun toString() = "XX"

  // Makes it more difficult for developers to invoke sensitive data
  // and facilitates sensitive data usage tracking.
  fun getDataToMask(): T = data
}

data class Person(
  val email: ToMask<String>,
  val username: String
)

fun main() {
    val person = Person(
        ToMask("name@gmail.com"),
        "myname"
    )
    println(person)
    println(person.email.getDataToMask())
}

Java

public class ToMask<T> {
  // Prevents accidental logging when an error is encountered.
  public String toString(){
         return "XX";
  }

  // Makes it more difficult for developers to invoke sensitive data
  // and facilitates sensitive data usage tracking.
  public T  getDataToMask() {
    return this;
  }
}

public class Person {
  private ToMask<String> email;
  private String username;

  public Person(ToMask<String> email, String username) {
    this.email = email;
    this.username = username;
  }
}

public static void main(String[] args) {
    Person person = new Person(
        ToMask("name@gmail.com"),
        "myname"
    );
    System.out.println(person);
    System.out.println(person.email.getDataToMask());
}