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

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

نظرة عامة

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

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

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

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

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

التأثير

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

إجراءات التخفيف

بنود عامة

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

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

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

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

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

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

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

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

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

إزالة السجلات من logcat من إصدارات الإصدار العلني باستخدام R8

في الإصدار 3.4 من "استوديو Android" أو الإصدار 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());
}