קטגוריה ב-OWASP: MASVS-STORAGE: אחסון
סקירה כללית
חשיפת פרטי יומן היא סוג של נקודת חולשה שבה אפליקציות מדפיסות מידע רגיש ביומן המכשיר. אם המידע הרגיש הזה נחשף לגורמים זדוניים, הוא עשוי להיות בעל ערך ממשי – כמו פרטי הכניסה של משתמש או פרטים אישיים מזהים (PII) – או שהוא עשוי לאפשר התקפות נוספות.
הבעיה הזו יכולה להתרחש בכל אחד מהתרחישים הבאים:
- יומנים שנוצרו על ידי האפליקציה:
- היומנים מאפשרים בכוונה גישה לגורמים לא מורשים, אבל הם מכילים בטעות מידע רגיש.
- היומנים כוללים מידע רגיש בכוונה, אבל גורמים לא מורשים יכולים לגשת אליהם בטעות.
- יומני שגיאות כלליים שעשויים לפעמים להדפיס מידע רגיש, בהתאם להודעת השגיאה שהופיעה.
- יומנים שנוצרו באופן חיצוני:
- רכיבים חיצוניים אחראים להדפסת יומנים שכוללים מידע אישי רגיש.
משפטי Log.*
ב-Android כותבים למאגר הזיכרון המשותף logcat
. החל מגרסה 4.1 של Android (רמת API 16), רק לאפליקציות מערכת בעלות הרשאות ניתן להעניק גישה לקריאת logcat
, על ידי הצהרה על ההרשאה READ_LOGS
. עם זאת, Android תומך במגוון עצום של מכשירים, שבהם האפליקציות שמותקנות מראש מכריזות לפעמים על ההרשאה READ_LOGS
. לכן, לא מומלץ לתעד ישירות ב-logcat
כי יש סיכוי גבוה יותר לדליפה של נתונים.
חשוב לוודא שכל הרישום ביומן ב-logcat
עבר תהליך טיהור בגרסאות של האפליקציה שלא מיועדות לניפוי באגים. מסירים נתונים שעשויים להיות רגישים. כאמצעי זהירות נוסף, כדאי להשתמש בכלים כמו R8 כדי להסיר את כל רמות היומן מלבד אזהרה ושגיאה. אם אתם צריכים יומנים מפורטים יותר, השתמשו באחסון פנימי וניהול היומנים שלכם ישירות, במקום להשתמש ביומן המערכת.
השפעה
חומרת הכשל של סיווג נקודת החולשה 'חשיפת פרטי יומן' משתנה בהתאם להקשר ולסוג המידע הרגיש. באופן כללי, ההשפעה של סיווג נקודת החולשה הזה היא אובדן הסודיות של מידע שעלול להיות קריטי, כמו פרטים אישיים מזהים (PII) ופרטי כניסה.
פעולות מיטיגציה
כללי
כצעד מניעתי כללי בתכנון ובהטמעה, כדאי להגדיר גבולות אמון בהתאם לעיקרון של הרשאות מינימליות. באופן אידיאלי, מידע רגיש לא אמור לעבור או להגיע מחוץ לאזורי האמון. כך מחזקים את ההפרדה בין ההרשאות.
לא לתעד ביומן מידע אישי רגיש. כשהדבר אפשרי, כדאי לתעד ביומן רק קבועים בזמן הידור. אפשר להשתמש בכלי ErrorProne כדי להוסיף הערות לקבועים בזמן הידור.
הימנעו מיומנים שמדפיסים הצהרות שעשויות להכיל מידע בלתי צפוי, כולל מידע אישי רגיש, בהתאם לשגיאה שהופיעה. ככל האפשר, הנתונים שמודפסים ביומנים וביומני השגיאות צריכים לכלול רק מידע צפוי.
הימנעו מרישום ביומן ב-logcat
. הסיבה לכך היא שדיווח ביומן ל-logcat
עלול להפוך לבעיית פרטיות בגלל אפליקציות עם ההרשאה READ_LOGS
. בנוסף, הוא לא יעיל כי אי אפשר להפעיל התראות או לשלוח שאילתות לגבי הנתונים שלו. מומלץ להגדיר את הקצה העורפי של logcat
באפליקציות לגרסת build למפתחים בלבד.
רוב ספריות ניהול היומנים מאפשרות להגדיר רמות יומן, וכך אפשר לתעד כמויות שונות של מידע בין יומני ניפוי באגים לבין יומני ייצור. משנים את רמת היומן כך שלא תהיה זהה ל'ניפוי באגים' ברגע שבדיקת המוצר מסתיימת.
מומלץ להסיר כמה שיותר רמות יומן בסביבת הייצור. אם אי אפשר להימנע משמירת יומנים בסביבת הייצור, צריך להסיר משתנים שאינם קבועים מהצהרות היומן. אלה התרחישים האפשריים:
- אפשר להסיר את כל היומנים מ-Production.
- צריך לשמור יומני אזהרות ושגיאות בסביבת הייצור.
בשני המקרים האלה, צריך להסיר יומנים באופן אוטומטי באמצעות ספריות כמו R8. ניסיונות להסיר יומנים באופן ידני עלולים לגרום לשגיאות. כחלק מאופטימיזציית הקוד, אפשר להגדיר את R8 כך שיסיר בבטחה רמות יומן שרוצים לשמור לצורך ניפוי באגים, אבל יגרום להסרה שלהן בסביבת הייצור.
אם אתם מתכוונים לתעד ביומן בסביבת הייצור, כדאי להכין דגלים שתוכלו להשתמש בהם כדי להשבית את הרישום ביומן באופן מותנה במקרה של אירוע. כשמשתמשים בדגלים של תגובת אירוע, צריך לתת עדיפות לגורמים הבאים: בטיחות הפריסה, מהירות וקלילות הפריסה, השלמות של השמטת פרטים ביומני אירועים, שימוש בזיכרון ועלויות הביצועים של סריקת כל הודעת יומן.
הסרת יומנים ל-logcat מגרסאות build בסביבת הייצור באמצעות 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
. לפני שמפרסמים את האפליקציה בסביבת הייצור, מומלץ להשלים תהליך של בדיקת אבטחה כדי לבדוק לעומק את השימוש בהסתרת נתונים. אזהרה: אל תשתמשו בהסוואת נתונים במקרים שבהם גם פרסום של חלק מהמידע הרגיש עלול להשפיע באופן משמעותי על האבטחה, למשל בטיפול בסיסמות. - צנזור השמטה דומה למס masking, אבל היא מסתירה את כל המידע שמכיל השדה. דוגמה: החלפת מספר כרטיס האשראי
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());
}