الإفصاح عن معلومات السجلّ

فئة OWASP: MASVS-STORAGE: التخزين

نظرة عامة

الكشف عن معلومات السجلّ هو نوع من الثغرات الأمنية التي تطبع فيها التطبيقات بيانات حساسة في سجلّ الجهاز. إذا تم الكشف عن هذه المعلومات الحساسة للجهات المسيئة، قد تكون قيّمة بحد ذاتها، مثل بيانات اعتماد المستخدم أو المعلومات التي تكشف الهوية الشخصية (PII)، أو قد تتيح شنّ المزيد من الهجمات.

يمكن أن تحدث هذه المشكلة في أي من السيناريوهات التالية:

  • السجلّات التي ينشئها التطبيق:
    • تسمح السجلّات عمدًا بالوصول إلى الجهات غير المصرَّح لها، ولكنها تحتوي عن طريق الخطأ على بيانات حساسة.
    • تتضمّن السجلّات عمدًا بيانات حساسة، ولكن يمكن للجهات غير المصرَّح لها الوصول إليها عن طريق الخطأ.
    • سجلّات الأخطاء العامة التي قد تطبع أحيانًا بيانات حساسة، حسب رسالة الخطأ التي يتم تشغيلها.
  • السجلّات التي يتم إنشاؤها خارجيًا:
    • المكوّنات الخارجية هي المسؤولة عن طباعة السجلّات التي تتضمّن بيانات حساسة.

تكتب عبارات Log.* في Android إلى مخزن الذاكرة المؤقتة المشتركة logcat. اعتبارًا من Android 4.1 (مستوى واجهة برمجة التطبيقات 16)، لا يمكن منح إذن قراءة logcat إلا لتطبيقات النظام المميّزة، وذلك من خلال الإعلان عن إذن READ_LOGS. ومع ذلك، يتيح Android مجموعة متنوعة بشكل لا يُصدّق من الأجهزة التي تعلن التطبيقات المحمّلة مسبقًا عليها أحيانًا عن إذن READ_LOGS. نتيجةً لذلك، لا يُنصح بتسجيل البيانات مباشرةً في logcat لأنّها أكثر عرضة لتسرّب البيانات.

تأكَّد من إزالة أي بيانات حساسة من جميع السجلّات في logcat في الإصدارات غير المخصّصة لتصحيح الأخطاء من تطبيقك. أزِل أي بيانات قد تكون حساسة. كإجراء وقائي إضافي، استخدِم أدوات مثل R8 لإزالة جميع مستويات السجلّات باستثناء التحذيرات والأخطاء. إذا كنت بحاجة إلى سجلّات أكثر تفصيلاً، استخدِم وحدة التخزين الداخلية وأدِر سجلّاتك بنفسك مباشرةً بدلاً من استخدام سجلّ النظام.

التأثير

يمكن أن تختلف درجة خطورة فئة الثغرات الأمنية "الكشف عن معلومات السجلّ" حسب السياق ونوع البيانات الحساسة. وبشكل عام، يؤدي هذا النوع من الثغرات الأمنية إلى فقدان سرية المعلومات التي قد تكون بالغة الأهمية، مثل المعلومات التي تكشف الهوية الشخصية وبيانات الاعتماد.

إجراءات الحماية

بنود عامة

كإجراء وقائي عام أثناء التصميم والتنفيذ، ارسم حدود الثقة وفقًا لـ مبدأ الحدّ الأدنى من الأذونات المميّزة. من الناحية المثالية، يجب ألا تعبر البيانات الحساسة أو تصل إلى خارج أي من مناطق الثقة. ويعزّز ذلك فصل الأذونات المميّزة.

لا تسجِّل البيانات الحساسة. سجِّل الثوابت في وقت التجميع فقط كلما أمكن ذلك. يمكنك استخدام أداة ErrorProne لإضافة تعليقات توضيحية للثوابت في وقت التجميع.

تجنَّب السجلّات التي تطبع عبارات قد تحتوي على معلومات غير متوقّعة، بما في ذلك البيانات الحساسة، حسب الخطأ الذي يتم تشغيله. يجب ألا تتضمّن البيانات المطبوعة في السجلّات وسجلّات الأخطاء إلا معلومات يمكن توقّعها قدر الإمكان.

تجنَّب تسجيل البيانات في logcat. قد يصبح تسجيل البيانات في logcat مشكلة تتعلق بالخصوصية بسبب التطبيقات التي لديها إذن READ_LOGS. كما أنّه غير فعّال لأنّه لا يمكنه تشغيل التنبيهات أو الاستعلام عنه. ننصح التطبيقات بضبط واجهة logcat الخلفية لإصدارات المطوّرين فقط.

تسمح معظم مكتبات إدارة السجلّات بتحديد مستويات السجلّات، ما يسمح بتسجيل كميات مختلفة من المعلومات بين سجلّات تصحيح الأخطاء وسجلّات الإنتاج. غيِّر مستوى السجلّ بحيث يختلف عن "تصحيح الأخطاء" بمجرد انتهاء اختبار المنتج.

أزِل أكبر عدد ممكن من مستويات السجلّات من الإنتاج. إذا لم يكن بإمكانك تجنُّب الاحتفاظ بالسجلّات في الإنتاج، أزِل المتغيّرات غير الثابتة من عبارات السجلّ. قد تحدث السيناريوهات التالية:

  • يمكنك إزالة جميع السجلّات من الإنتاج.
  • عليك الاحتفاظ بسجلّات التحذيرات والأخطاء في الإنتاج.

في كلتا الحالتَين، أزِل السجلّات تلقائيًا باستخدام مكتبات مثل R8. أي محاولات لإزالة السجلّات يدويًا تكون عرضة للخطأ. كجزء من تحسين الرمز البرمجي، يمكن ضبط R8 لإزالة مستويات السجلّات بأمان التي تريد الاحتفاظ بها لتصحيح الأخطاء، ولكن إزالتها في الإنتاج.

إذا كنت ستسجِّل البيانات في الإنتاج، أعدّ العلامات التي يمكنك استخدامها لإيقاف تسجيل البيانات بشكل مشروط في حال وقوع حادث. يجب أن تعطي علامات الاستجابة للحوادث الأولوية لما يلي: سلامة النشر وسرعة النشر وسهولته، ودقة إخفاء السجلّات، واستخدام الذاكرة، وتكاليف الأداء لفحص كل رسالة سجلّ.

أزِل السجلّات من `logcat` من إصدارات الإنتاج باستخدام R8.

في "استوديو Android" 3.4 أو الإصدار 3.4.0 من مكوّن إضافي لنظام Gradle المتوافق مع Android والإصدارات الأحدث، يكون R8 هو برنامج التجميع التلقائي لتحسين الرمز البرمجي وتقليصه. ومع ذلك، عليك تفعيل R8.

لقد حلّ R8 محل ProGuard، ولكن لا يزال اسم ملف القواعد في المجلد الجذر للمشروع هو proguard-rules.pro.يعرض المقتطف التالي نموذجًا لملف proguard-rules.pro يزيل جميع السجلّات من الإنتاج باستثناء التحذيرات والأخطاء:

-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");
}

يزيل نموذج ملف proguard-rules.pro التالي جميع السجلّات من الإنتاج:

-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");
}

يُرجى العِلم أنّ R8 يوفّر إمكانات تقليص التطبيق ووظيفة إزالة السجلّات. إذا كنت تريد استخدام R8 فقط لوظيفة إزالة السجلّات، أضِف ما يلي إلى ملف proguard-rules.pro:

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

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

أزِل أي بيانات حساسة من السجلّات المحتمَلة في الإنتاج

لتجنُّب تسرّب البيانات الحساسة، تأكَّد من إزالة أي بيانات حساسة من جميع السجلّات في logcat في الإصدارات غير المخصّصة لتصحيح الأخطاء من تطبيقك. أزِل أي بيانات قد تكون حساسة.

مثال:

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

إخفاء البيانات الحساسة في السجلّات

إذا كان عليك تضمين بيانات حساسة في سجلّاتك، ننصحك بإزالة أي بيانات حساسة من السجلّات قبل طباعتها لإزالتها أو إخفاءها. لإجراء ذلك، استخدِم إحدى التقنيات التالية:

  • الترميز. إذا تم تخزين البيانات الحساسة في خزنة، مثل نظام إدارة التشفير الذي يمكن الرجوع إلى الأسرار منه باستخدام الرموز المميّزة، سجِّل الرمز المميّز بدلاً من البيانات الحساسة.
  • إخفاء البيانات. إخفاء البيانات هو عملية أحادية الاتجاه لا رجعة فيها. تنشئ هذه العملية إصدارًا من البيانات الحساسة يبدو مشابهًا من الناحية الهيكلية للبيانات الأصلية، ولكنها تخفي المعلومات الأكثر حساسية الواردة في أحد الحقول. مثال: استبدال رقم بطاقة الائتمان 1234-5678-9012-3456 بالرقم XXXX-XXXX-XXXX-1313. قبل إطلاق تطبيقك في الإنتاج، ننصحك بإكمال عملية مراجعة أمان للتدقيق في استخدام إخفاء البيانات. تحذير: لا تستخدِم إخفاء البيانات في الحالات التي يمكن أن يؤثر فيها بشكل كبير على الأمان حتى إصدار جزء فقط من البيانات الحساسة، مثل عند معالجة كلمات المرور.
  • الإخفاء. يشبه الإخفاء عملية إخفاء البيانات، ولكنه يخفي جميع المعلومات الواردة في أحد الحقول. مثال: استبدال رقم بطاقة الائتمان 1234-5678-9012-3456 بالرقم XXXX-XXXX-XXXX-XXXX.
  • الفلترة. نفِّذ سلاسل التنسيق في مكتبة تسجيل البيانات التي تختارها إذا لم تكن متوفّرة من قبل، لتسهيل تعديل القيم غير الثابتة في عبارات السجلّ.

يجب ألا تتم طباعة السجلّات إلا من خلال مكوّن "إزالة البيانات الحساسة من السجلّات" الذي يضمن إزالة أي بيانات حساسة من جميع السجلّات قبل طباعتها، كما هو موضّح في مقتطف الرمز البرمجي التالي.

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