קטגוריית OWASP: MASVS-CODE: איכות הקוד
סקירה כללית
לא נדיר למצוא אפליקציות שמטמיעות פונקציונליות שמאפשרת למשתמשים להעביר נתונים או ליצור אינטראקציה עם מכשירים אחרים באמצעות תקשורת בתדר רדיו (RF) או חיבורים קוויים. הטכנולוגיות הנפוצות ביותר מכשירי Android למטרה הזו הם Bluetooth קלאסי (Bluetooth BR/EDR), Bluetooth Low אנרגיה (BLE), Wi-Fi P2P, NFC ו-USB.
הטכנולוגיות האלה מוטמעות בדרך כלל באפליקציות שצפויות תקשורת עם אביזרים לבית חכם, מכשירים לניטור מצב בריאותי, ציבורי קיוסקים לתחבורה, מסופי תשלום ומכשירים אחרים מבוססי Android.
כמו בכל ערוץ אחר, תקשורת בין מכונות חשופה להתקפות שמטרתן לפגוע בגבול האמון שנוצר בין שני מכשירים או יותר. אפשר להשתמש בטכניקות כמו התחזות למכשיר משתמשים זדוניים כדי להשיג מספר גדול של מתקפות נגד התקשורת .
Android מאפשר למפתחים להשתמש בממשקי API ספציפיים להגדרת תקשורת בין מכונות.
צריך להשתמש בממשקי ה-API האלה בזהירות, כי שגיאות בהטמעת פרוטוקולי התקשורת עלולות לגרום לחשיפת נתוני משתמשים או מכשירים לצדדים שלישיים לא מורשים. במקרה הגרוע ביותר, ייתכן שתוקפים יוכלו לבצע מרחוק להשתלט על מכשיר אחד או יותר, וכתוצאה מכך לקבל גישה מלאה לתוכן במכשיר.
השפעה
ההשפעה עשויה להשתנות בהתאם לטכנולוגיה של תקשורת בין מכשירים שמוטמעת באפליקציה.
שימוש שגוי או הגדרה שגויה של ערוצי תקשורת בין מכונות עלולים לחשוף את מכשיר המשתמש לניסיונות תקשורת לא מהימנים. כתוצאה מכך, המכשיר עלול להיות חשוף להתקפות נוספות, כמו התקפת אדם בתווך (MiTM), הזרקת פקודות, התקפת מניעת שירות (DoS) או התקפת התחזות.
סיכון: האזנה למידע רגיש בערוצים אלחוטיים
כשמפעילים מנגנוני תקשורת בין מכונות, צריך להביא בחשבון גם את הטכנולוגיה שבה משתמשים וגם את סוג הנתונים שצריך להעביר. חיבורים קוויים הם בטוחים יותר למשימות כאלה, כי הם דורשים קישור פיזי בין המכשירים המעורבים, אבל אפשר ליירט פרוטוקולי תקשורת שמשתמשים בתדרי רדיו כמו Bluetooth קלאסי, BLE, NFC ו-Wi-Fi P2P. תוקף יכול להתחזות לאחד מהמסופים או לנקודות הגישה שמעורבים בחילופי הנתונים, ליירט את התקשורת האווירית ולקבל גישה לנתוני משתמשים רגישים. בנוסף, אם אפליקציות זדוניות שמותקנות במכשיר יקבלו את ההרשאות בסביבת זמן הריצה הספציפיות לתקשורת, הן עשויות לקרוא את מאגרי ההודעות של המערכת כדי לאחזר נתונים שמתחלפים בין המכשירים.
פעולות מיטיגציה
אם האפליקציה דורשת העברה של מידע רגיש ממכונה למכונה בערוצים אלחוטיים, צריך להטמיע בקוד של האפליקציה פתרונות אבטחה בשכבת האפליקציה, כמו הצפנה. כך תוכלו למנוע מתוקפים לרחרח את ערוץ התקשורת ולשלוף את הנתונים המוחלפים בטקסט ללא הצפנה. למקורות מידע נוספים, אפשר לעיין מסמכי תיעוד בנושא קריפטוגרפיה.
סיכון: החדרת נתונים זדוניים אלחוטית
ניתן לשבש ערוצי תקשורת אלחוטיים בין מכונות (Bluetooth קלאסי, BLE, NFC, Wifi P2P) באמצעות נתונים זדוניים. בעל/ת מספיק מיומנות תוקפים יכולים לזהות את פרוטוקול התקשורת שנמצא בשימוש ולשנות את של העברת נתונים, לדוגמה על ידי התחזות לאחת מנקודות הקצה, מטענים ייעודיים (payloads) שנוצרו במיוחד. תנועה זדונית כזו עלולה לפגוע בפונקציונליות של האפליקציה, ובמקרה הגרוע ביותר לגרום להתנהגות בלתי צפויה של האפליקציה והמכשיר, או להתקפות כמו התקפת מניעת שירות (DoS), הזרקת פקודות או השתלטות על המכשיר.
פעולות מיטיגציה
מערכת Android מספקת למפתחים ממשקי API עוצמתיים תקשורת ממכונה למכונה, כמו Bluetooth הקלאסי, BLE, NFC ו-Wi-Fi P2P. צריך לשלב אותן עם לוגיקה של אימות נתונים שהוטמעה בקפידה כדי לחיטוי נתונים שמועברים בין שני מכשירים.
צריך להטמיע את הפתרון הזה ברמת האפליקציה, ולכלול בו שבודק אם הנתונים הם באורך ובפורמט הצפויים ומכילים מטען ייעודי (payload) חוקי שניתן לפרש על ידי האפליקציה.
בקטע הקוד הבא מוצגת לוגיקה לדוגמה של אימות נתונים. היא הוטמעה מעל הדוגמה של מפתחי Android להטמעת נתוני Bluetooth העברה:
Kotlin
class MyThread(private val mmInStream: InputStream, private val handler: Handler) : Thread() {
private val mmBuffer = ByteArray(1024)
override fun run() {
while (true) {
try {
val numBytes = mmInStream.read(mmBuffer)
if (numBytes > 0) {
val data = mmBuffer.copyOf(numBytes)
if (isValidBinaryData(data)) {
val readMsg = handler.obtainMessage(
MessageConstants.MESSAGE_READ, numBytes, -1, data
)
readMsg.sendToTarget()
} else {
Log.w(TAG, "Invalid data received: $data")
}
}
} catch (e: IOException) {
Log.d(TAG, "Input stream was disconnected", e)
break
}
}
}
private fun isValidBinaryData(data: ByteArray): Boolean {
if (// Implement data validation rules here) {
return false
} else {
// Data is in the expected format
return true
}
}
}
Java
public void run() {
mmBuffer = new byte[1024];
int numBytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs.
while (true) {
try {
// Read from the InputStream.
numBytes = mmInStream.read(mmBuffer);
if (numBytes > 0) {
// Handle raw data directly
byte[] data = Arrays.copyOf(mmBuffer, numBytes);
// Validate the data before sending it to the UI activity
if (isValidBinaryData(data)) {
// Data is valid, send it to the UI activity
Message readMsg = handler.obtainMessage(
MessageConstants.MESSAGE_READ, numBytes, -1,
data);
readMsg.sendToTarget();
} else {
// Data is invalid
Log.w(TAG, "Invalid data received: " + data);
}
}
} catch (IOException e) {
Log.d(TAG, "Input stream was disconnected", e);
break;
}
}
}
private boolean isValidBinaryData(byte[] data) {
if (// Implement data validation rules here) {
return false;
} else {
// Data is in the expected format
return true;
}
}
סיכון: הזרקת נתונים זדוניים באמצעות USB
משתמש זדוני יכול לטרגט חיבורי USB בין שני מכשירים שרוצים ליירט תקשורת. במקרה כזה, הקישור הפיזי הנדרש מהווה שכבת אבטחה נוספת, כי התוקף צריך לקבל גישה לכבל שמחבר את המסופים כדי לטלפן ללא רשות. כיוון התקפה נוסף הוא מכשירי USB לא מהימנים שמחוברים למכשיר, בכוונה או בטעות.
אם האפליקציה מסננת מכשירי USB באמצעות PID/VID כדי להפעיל פונקציונליות ספציפית באפליקציה, תוקפים עשויים לזייף את המכשיר החוקי כדי לשבש את הנתונים שנשלחים דרך ערוץ ה-USB. התקפות מהסוג הזה לאפשר למשתמשים זדוניים לשלוח הקשות למכשיר או להפעיל אפליקציה פעילויות ש, במקרה הגרוע ביותר, עלולות להוביל לביצוע קוד מרחוק או או הורדה של תוכנה לא רצויה.
מיטיגציות
צריך להטמיע לוגיקה של אימות ברמת האפליקציה. הלוגיקה הזו לסנן את הנתונים שנשלחים באמצעות USB תוך בדיקה שהאורך, הפורמט והתוכן תואמים לתרחיש לדוגמה של האפליקציה. לדוגמה, מכשיר למעקב אחר קצב הלב לא אמור לשלוח פקודות של הקשות.
בנוסף, כשהדבר אפשרי, חשוב לשקול הגבלה של מספר חבילות ה-USB שהאפליקציה יכולה לקבל מהתקן ה-USB. כך אפשר למנוע ממכשירים זדוניים לבצע התקפות כמו Rubber Ducky.
אפשר לבצע את האימות הזה על-ידי יצירת שרשור חדש לבדיקת השרשורים
באמצעות מאגר נתונים זמני, לדוגמה, בbulkTransfer
:
Kotlin
fun performBulkTransfer() {
// Stores data received from a device to the host in a buffer
val bytesTransferred = connection.bulkTransfer(endpointIn, buffer, buffer.size, 5000)
if (bytesTransferred > 0) {
if (//Checks against buffer content) {
processValidData(buffer)
} else {
handleInvalidData()
}
} else {
handleTransferError()
}
}
Java
public void performBulkTransfer() {
//Stores data received from a device to the host in a buffer
int bytesTransferred = connection.bulkTransfer(endpointIn, buffer, buffer.length, 5000);
if (bytesTransferred > 0) {
if (//Checks against buffer content) {
processValidData(buffer);
} else {
handleInvalidData();
}
} else {
handleTransferError();
}
}
סיכונים ספציפיים
בקטע הזה מפורטים סיכונים שדורשים אסטרטגיות מיטיגציה לא סטנדרטיות או שצומצמו ברמת SDK מסוימת, והם מופיעים כאן לצורך השלמות.
סיכון: Bluetooth – זמן גילוי שגוי
כפי שמודגש בתיעוד ה-Bluetooth למפתחי Android.
להגדיר את ממשק Bluetooth בתוך האפליקציה, באמצעות ה
השיטה startActivityForResult(Intent, int)
להפעלת המכשיר
ואת יכולת הגילוי של EXTRA_DISCOVERABLE_DURATION
לגרום לחשיפת המכשיר כל עוד האפליקציה פועלת
ברקע או בחזית. במפרט Bluetooth הקלאסי, מכשירים גלויים משדרים כל הזמן הודעות גילוי ספציפיות שמאפשרות למכשירים אחרים לאחזר את נתוני המכשיר או להתחבר אליו. לחשבון
כזה, צד שלישי זדוני יכול ליירט הודעות כאלה ולהתחבר
למכשיר שמופעל בו מערכת Android. אחרי החיבור, תוקף יכול לבצע התקפות נוספות, כמו גניבת נתונים, התקפת מניעת שירות (DoS) או הזרקת פקודות.
פעולות מיטיגציה
הערך של EXTRA_DISCOVERABLE_DURATION
אף פעם לא צריך להיות אפס. אם
הפרמטר EXTRA_DISCOVERABLE_DURATION
לא מוגדר, כברירת מחדל, מערכת Android יוצרת
המכשירים יהיו גלויים למשך 2 דקות. הערך המקסימלי שניתן להגדיר לפרמטר EXTRA_DISCOVERABLE_DURATION
הוא שעתיים (7,200 שניות). זה כן
מומלץ לשמור על משך הזמן הקצר ביותר שגלוי לכולם
בהתאם לתרחיש לדוגמה של האפליקציה.
סיכון: NFC – מסנני Intent משוכפלים
אפליקציה זדונית יכולה לרשום מסנני כוונה (intent) כדי לקרוא תגי NFC ספציפיים או מכשירי NFC ספציפיים. המסננים האלה יכולים לחקות את אלה שהוגדרו באמצעות אפליקציה לגיטימית שמאפשרת לתוקפים לקרוא את התוכן של נתוני ה-NFC שהוחלפו. חשוב לציין שכאשר שתי פעילויות מציינות את אותם מסנני כוונה לתג NFC ספציפי, מוצג בורר הפעילויות, ולכן המשתמש עדיין יצטרך לבחור את האפליקציה הזדונית כדי שההתקפה תצליח. למרות זאת, שילוב מסנני Intent עם הסוואה, תרחיש זה עדיין אפשרי. ההתקפה הזו רלוונטית רק במקרים שבהם הנתונים שמתחלפים דרך NFC נחשבים לרגישים מאוד.
פעולות מיטיגציה
כשמטמיעים יכולות קריאה של NFC באפליקציה, אפשר להשתמש במסנני כוונה יחד עם רשומות של אפליקציות ל-Android (AAR). הטמעת הרשומה AAR בתוך הודעת NDEF תבטיח באופן משמעותי שהאפליקציה החוקית והפעילות המשויכת לטיפול ב-NDEF יופעלו בלבד. זה ימנע מאפליקציות או מפעילויות לא רצויות לקרוא ערכים גבוהים של תג או נתונים רגישים במכשיר שמועברים דרך NFC.
סיכון: NFC – חוסר אימות של הודעות NDEF
כשמכשיר מבוסס-Android מקבל נתונים מתג NFC או מתג NFC במכשיר, המערכת מפעילה באופן אוטומטי את האפליקציה פעילות שמוגדרת לטפל בהודעת ה-NDEF שבתוכה. בהתאם ללוגיקה שמוטמעת באפליקציה, הנתונים שנכללים את התג או שהתקבלו מהמכשיר, יכולים להיות מוצגים לפעילויות אחרות כדי להפעיל פעולות נוספות, כמו פתיחת דפי אינטרנט.
אפליקציה ללא אימות של תוכן ההודעה מסוג NDEF עלולה לאפשר לתוקפים: להשתמש במכשירים שתומכים ב-NFC או בתגי NFC כדי להחדיר מטענים ייעודיים (payloads) זדוניים בתוך גורם להתנהגות בלתי צפויה, שעשויה לגרום לקובץ זדוני הורדה, החדרת פקודה או DoS.
פעולות מיטיגציה
לפני שליחה של הודעת ה-NDEF שהתקבלה לכל רכיב אחר של האפליקציה, יש לאמת את הנתונים שבתבנית כך שיהיו בפורמט הרצוי ולהכיל את מידע מצופה. כך ניתן למנוע העברה של נתונים זדוניים אפליקציות ללא סינון, ותפחית את הסיכון להתנהגות בלתי צפויה תוקף באמצעות נתוני NFC שעברו שינוי.
קטע הקוד הבא מציג דוגמה ללוגיקת אימות נתונים שמופעלת כשיטה עם הודעת NDEF כארגומנטים והאינדקס שלה במערך ההודעות. הטמענו את הקוד הזה על הדוגמה למפתחי Android כדי לקבל נתונים מתג NFC NDEF שנסרק:
Kotlin
//The method takes as input an element from the received NDEF messages array
fun isValidNDEFMessage(messages: Array<NdefMessage>, index: Int): Boolean {
// Checks if the index is out of bounds
if (index < 0 || index >= messages.size) {
return false
}
val ndefMessage = messages[index]
// Retrieves the record from the NDEF message
for (record in ndefMessage.records) {
// Checks if the TNF is TNF_ABSOLUTE_URI (0x03), if the Length Type is 1
if (record.tnf == NdefRecord.TNF_ABSOLUTE_URI && record.type.size == 1) {
// Loads payload in a byte array
val payload = record.payload
// Declares the Magic Number that should be matched inside the payload
val gifMagicNumber = byteArrayOf(0x47, 0x49, 0x46, 0x38, 0x39, 0x61) // GIF89a
// Checks the Payload for the Magic Number
for (i in gifMagicNumber.indices) {
if (payload[i] != gifMagicNumber[i]) {
return false
}
}
// Checks that the Payload length is, at least, the length of the Magic Number + The Descriptor
if (payload.size == 13) {
return true
}
}
}
return false
}
Java
//The method takes as input an element from the received NDEF messages array
public boolean isValidNDEFMessage(NdefMessage[] messages, int index) {
//Checks if the index is out of bounds
if (index < 0 || index >= messages.length) {
return false;
}
NdefMessage ndefMessage = messages[index];
//Retrieve the record from the NDEF message
for (NdefRecord record : ndefMessage.getRecords()) {
//Check if the TNF is TNF_ABSOLUTE_URI (0x03), if the Length Type is 1
if ((record.getTnf() == NdefRecord.TNF_ABSOLUTE_URI) && (record.getType().length == 1)) {
//Loads payload in a byte array
byte[] payload = record.getPayload();
//Declares the Magic Number that should be matched inside the payload
byte[] gifMagicNumber = {0x47, 0x49, 0x46, 0x38, 0x39, 0x61}; // GIF89a
//Checks the Payload for the Magic Number
for (int i = 0; i < gifMagicNumber.length; i++) {
if (payload[i] != gifMagicNumber[i]) {
return false;
}
}
//Checks that the Payload length is, at least, the length of the Magic Number + The Descriptor
if (payload.length == 13) {
return true;
}
}
}
return false;
}
משאבים
- הרשאות בזמן ריצה
- מדריכי קישוריות
- דוגמה
- העברה בכמות גדולה
- קריפטוגרפיה
- הגדרת Bluetooth
- NFC Basis
- רשומות של אפליקציות ל-Android
- מפרט Bluetooth הקלאסי