גילוי נאות של פרטי היומן

קטגוריית OWASP: MASVS-STORAGE: אחסון

סקירה כללית

חשיפת פרטי יומן היא סוג של פגיעות שבה אפליקציות מדפיסות מידע אישי רגיש בקובץ יומן במכשיר. אם המידע הרגיש הזה ייחשף לגורמים זדוניים, הוא עשוי להיות בעל ערך רב – כמו פרטי הכניסה של משתמש או פרטים אישיים מזהים (PII) – או שהוא עשוי לאפשר מתקפות נוספות.

הבעיה הזו יכולה להתרחש בכל אחד מהתרחישים הבאים:

  • יומנים שנוצרו על ידי האפליקציה:
    • היומנים מאפשרים גישה לגורמים לא מורשים, אבל הם מכילים בטעות נתונים רגישים.
    • היומנים כוללים בכוונה נתונים רגישים, אבל גורמים לא מורשים יכולים לגשת אליהם בטעות.
    • יומני שגיאות כלליים שעשויים להדפיס מדי פעם מידע אישי רגיש, בהתאם להודעת השגיאה שהופעלה.
  • יומנים שנוצרו חיצונית:
    • רכיבים חיצוניים אחראים להדפסת יומנים שכוללים מידע אישי רגיש.

הצהרות של Android Log.* נכתבות למאגר הזיכרון המשותף logcat. מאז Android 4.1 (רמת API‏ 16), רק לאפליקציות מערכת בעלות הרשאות אפשר להעניק גישה לקריאת logcat, על ידי הצהרה על ההרשאה READ_LOGS. עם זאת, מערכת Android תומכת במגוון רחב של מכשירים, ובאפליקציות שנטענות מראש במכשירים האלה, לפעמים מוצהרת הרשאת READ_LOGS. לכן, לא מומלץ להתחבר ישירות ל-logcat כי יש סיכון גבוה יותר לדליפת נתונים.

חשוב לוודא שכל הרישום ביומן logcat עובר ניקוי בגרסאות שאינן גרסאות ניפוי באגים של האפליקציה. הסרת כל הנתונים שעשויים להיות רגישים. כאמצעי זהירות נוסף, מומלץ להשתמש בכלים כמו R8 כדי להסיר את כל רמות היומן מלבד אזהרה ושגיאה. אם אתם צריכים יומנים מפורטים יותר, אתם יכולים להשתמש באחסון פנימי ולנהל את היומנים שלכם ישירות, במקום להשתמש ביומן המערכת.

השפעה

החומרה של סיווג הפגיעות Log Info Disclosure (חשיפת מידע ביומן) יכולה להשתנות בהתאם להקשר ולסוג הנתונים הרגישים. באופן כללי, ההשפעה של סיווג הפגיעות הזה היא אובדן הסודיות של מידע שעלול להיות קריטי, כמו פרטים אישיים מזהים (PII) ופרטי כניסה.

אמצעי צמצום סיכונים

כללי

כאמצעי מניעתי כללי במהלך התכנון וההטמעה, כדאי להגדיר גבולות של אמון בהתאם לעקרון של הרשאות מינימליות. הכי טוב שמידע רגיש לא יחצה את אזורי האמון ולא יגיע מחוץ להם. כך מחזקים את ההפרדה בין ההרשאות.

אין לרשום ביומן מידע אישי רגיש. כשהדבר אפשרי, מתעדים רק קבועים בזמן ההידור. אפשר להשתמש בכלי ErrorProne להערות קבועות בזמן ההידור.

כדאי להימנע מיומנים שמדפיסים הצהרות שעשויות להכיל מידע לא צפוי, כולל מידע אישי רגיש, בהתאם לשגיאה שהופעלה. ככל האפשר, הנתונים שמודפסים ביומנים וביומני שגיאות צריכים לכלול רק מידע צפוי.

לא מומלץ להיכנס ל-logcat. הסיבה לכך היא שהתחברות ל-logcat עלולה להפוך לבעיה של פרטיות בגלל אפליקציות עם הרשאת READ_LOGS. היא גם לא יעילה כי אי אפשר להפעיל באמצעותה התראות או לשלוח אליה שאילתות. מומלץ להגדיר את ה-backend של logcat רק לגרסאות למפתחים של האפליקציות.

רוב ספריות ניהול היומנים מאפשרות להגדיר רמות יומן, וכך אפשר לרשום ביומן כמויות שונות של מידע בין יומני ניפוי באגים לבין יומני ייצור. משנים את רמת הרישום ביומן כך שהיא תהיה שונה מ-debug ברגע שהבדיקה של המוצר מסתיימת.

מומלץ להסיר כמה שיותר רמות של יומנים מהייצור. אם אין ברירה אלא לשמור יומנים בסביבת ייצור, צריך להסיר משפטי יומן משתנים לא קבועים. יכולים להתרחש התרחישים הבאים:

  • אפשר להסיר את כל היומנים מגרסת הייצור.
  • צריך לשמור את יומני האזהרות והשגיאות בייצור.

בשני המקרים האלה, צריך להסיר את היומנים באופן אוטומטי באמצעות ספריות כמו R8. כל ניסיון להסיר יומנים באופן ידני עלול להוביל לשגיאות. כחלק מאופטימיזציית הקוד, אפשר להגדיר את R8 כך שיסיר בבטחה רמות של יומנים שרוצים לשמור לצורך ניפוי באגים, אבל להסיר בייצור.

אם אתם מתכוונים להתחבר ל-Production, כדאי להכין דגלים שתוכלו להשתמש בהם כדי להפסיק את הרישום ביומן באופן מותנה במקרה של תקרית. הדגלים של תגובה לאירועים צריכים לתת עדיפות ל: בטיחות הפריסה, מהירות וקלות הפריסה, יסודיות הצנזורה של היומנים, השימוש בזיכרון ועלויות הביצועים של סריקת כל הודעת יומן.

הסרת יומנים מ-logcat מגרסאות ייצור באמצעות R8.

ב-Android Studio 3.4 או ב-פלאגין של Android Gradle 3.4.0 ואילך, ‏ 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());
}