فئة OWASP: MASVS-CODE: جودة الرمز
نظرة عامة
من النادر أن تجد تطبيقات تنفذ وظائف تتيح للمستخدمين نقل البيانات أو التفاعل مع أجهزة أخرى باستخدام الترددات اللاسلكية الاتصالات أو الاتصالات الكابلية. وتشمل التقنيات الأكثر شيوعًا المستخدَمة في Android لهذا الغرض البلوتوث الكلاسيكي (البلوتوث BR/EDR) والبلوتوث منخفض الطاقة (BLE) وWi-Fi P2P وNFC وUSB.
ويتم تنفيذ هذه التقنيات في التطبيقات التي من المتوقع أن التواصل مع ملحقات المنزل المزوّد بأجهزة ذكية، أجهزة مراقبة الصحة، الجمهور وأكشاك وسائل النقل ومحطات الدفع والأجهزة الأخرى التي تعمل بنظام التشغيل Android
كما هو الحال مع أي قناة أخرى، تكون الاتصالات بين الأجهزة التي تهدف إلى اختراق حدود الثقة القائمة بين اثنين أو المزيد من الأجهزة. يمكن للمستخدمين الضارّين الاستفادة من أساليب مثل انتحال هوية الجهاز لتنفيذ عدد كبير من الهجمات على قناة التواصل.
يصمّم Android واجهات برمجة تطبيقات معيّنة لضبط البيانات بين الأجهزة المختلفة. الاتصالات المتاحة للمطورين.
يجب استخدام واجهات برمجة التطبيقات هذه بعناية لأنّ الأخطاء أثناء تنفيذ بروتوكولات الاتصال قد تؤدي إلى تعريض بيانات المستخدمين أو بيانات الأجهزة لجهات خارجية غير مصرَّح بها. وفي أسوأ الحالات، قد يتمكن المهاجمون من الوصول إلى الاستيلاء على جهاز واحد أو أكثر، وبالتالي الحصول على إمكانية الوصول الكامل إلى المحتوى على الجهاز.
التأثير
قد يختلف التأثير حسب التكنولوجيا التي تربط الأجهزة معًا والتي تم تنفيذها في التطبيق.
قد يؤدي الاستخدام أو الضبط غير الصحيحَين لقنوات الاتصال بين الأجهزة إلى تعريض جهاز المستخدم لمحاولة اتصال غير موثوق بها. وقد يؤدي ذلك إلى تعرض الجهاز أو هجمات إضافية مثل الوسيط (MiTM) أو حقن الأوامر أو الحرمان من الخدمات أو هجمات انتحال الهوية.
الخطر: التنصت على البيانات الحسّاسة عبر القنوات اللاسلكية
عند تنفيذ آليات الاتصال من جهاز إلى آخر، يجب مراعاة بعناية كلّ من التكنولوجيا المستخدَمة ونوع البيانات التي يجب نقلها. على الرغم من أنّ عمليات الاتصال السلكي تكون في العادة أكثر أمانًا لمثل هذه المهام، لأنّها تتطلّب رابطًا ماديًا بين الأجهزة المعنيّة، يمكن اعتراض بروتوكولات الاتصالات التي تستخدم ترددات الراديو، مثل البلوتوث الكلاسيكي وBLE وNFC وWifi P2P. قد يتمكن المهاجم من انتحال هوية أحد المهاجمين الوحدات الطرفية أو نقاط الوصول المتضمنة في عملية تبادل البيانات، بهدف اعتراض التواصل عبر شبكة غير سلكيّة، وبالتالي الوصول إلى بيانات مستخدم حسّاس البيانات. بالإضافة إلى ذلك، إذا تم تثبيت التطبيقات الضارة على الجهاز قد تتمكن أذونات التشغيل الخاصة بالاتصال، من استرداد البيانات التي يتم تبادلها بين الأجهزة من خلال قراءة المخازن المؤقتة لرسائل النظام
إجراءات التخفيف
إذا كان التطبيق يتطلّب تبادل البيانات الحسّاسة بين الأجهزة عبر القنوات اللاسلكية، يجب تنفيذ حلول أمان على مستوى التطبيق، مثل التشفير، في رمز التطبيق. سيؤدي ذلك إلى منع المهاجمين من التقاط بيانات قناة الاتّصال واسترداد البيانات المتبادلة في نصّ عادي. للحصول على موارد إضافية، يُرجى الرجوع إلى مستندات التشفير
الخطر: حقن البيانات الضارّة عبر شبكة لاسلكية
يمكن التلاعب بقنوات الاتصال اللاسلكي بين الأجهزة (البلوتوث الكلاسيكي وBLE وNFC وWi-Fi P2P) باستخدام بيانات ضارة. يمكن للمهاجمين المهرة بشكلٍ كافٍ تحديد بروتوكول الاتصالات المستخدَم والتلاعب بمسار تبادل البيانات، على سبيل المثال عن طريق انتحال هوية إحدى نقاط النهاية وإرسال حمولات مصمّمة خصيصًا. قد يؤدي هذا النوع من الزيارات الضارة إلى خفض وظائف التطبيق، وفي أسوأ الحالات، قد يؤدي إلى سلوك غير متوقَّع للتطبيق والجهاز، أو قد يؤدي إلى هجمات مثل حجب الخدمة أو برمجة تعليمات أو الاستيلاء على الجهاز.
إجراءات التخفيف
يوفّر Android للمطوّرين واجهات برمجة تطبيقات فعّالة تتيح لهم إدارتها. الاتصالات بين الأجهزة، مثل البلوتوث الكلاسيكي وBLE وNFC وWi-Fi من P2P. ويجب دمج هذه الإجراءات مع منطق التحقّق من البيانات الذي يتم تنفيذه بعناية لتنظيف أي بيانات يتم تبادلها بين جهازَين.
يجب تنفيذ هذا الحل على مستوى التطبيق، ويجب أن يتضمّن عمليات تحقّق للتأكّد من أنّ البيانات ذات الطول والتنسيق المتوقّعَين وأنّها تحتوي على حمولة مفيدة válida يمكن للتطبيق تفسيرها.
يعرض المقتطف التالي مثالاً لمنطق التحقق من صحة البيانات. تم تنفيذ ذلك عبر مثال مطوّري برامج Android لتنفيذ بيانات البلوتوث التحويل:
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) ونحن هنا للتأكّد من اكتمالها.
خطر: البلوتوث - وقت قابلية الاكتشاف غير صحيح
كما هو موضّح في مستندات البلوتوث لمطوّري تطبيقات Android، أثناء
ضبط واجهة البلوتوث في التطبيق، يؤدي استخدام الأسلوب
startActivityForResult(Intent, int)
لتفعيل إمكانية
اكتشاف الجهاز وضبط EXTRA_DISCOVERABLE_DURATION
على القيمة صفر
إلى جعل الجهاز قابلاً للاكتشاف ما دام التطبيق قيد التشغيل سواءً
في المقدّمة أو الخلفية. بالنسبة إلى مواصفات البلوتوث الكلاسيكي، تبث الأجهزة القابلة للاكتشاف باستمرار رسائل إعلام تحديد هوية معيّنة تسمح للأجهزة الأخرى باسترداد بيانات الجهاز أو الاتصال به. ضِمن
في مثل هذا السيناريو، يمكن لجهة خارجية ضارة اعتراض مثل هذه الرسائل والاتصال
بجهاز Android. بمجرد الاتصال، يمكن للمهاجم تنفيذ المزيد
هجمات مثل سرقة البيانات أو الحرمان من الخدمات أو إدخال الأوامر.
إجراءات التخفيف
يجب عدم ضبط EXTRA_DISCOVERABLE_DURATION
على صفر أبدًا. في حال عدم ضبط المَعلمة
EXTRA_DISCOVERABLE_DURATION
، يجعل Android
الأجهزة قابلة للاكتشاف لمدة دقيقتين تلقائيًا. الحد الأقصى للقيمة التي يمكن ضبطها للمَعلمة
EXTRA_DISCOVERABLE_DURATION
هو ساعتان (7200 ثانية). ننصح بعدم إطالة مدة إمكانية العثور على التطبيق قدر الإمكان، وذلك وفقًا لحالة استخدام التطبيق.
الخطر: NFC – فلاتر الأهداف المستنسَخة
يمكن للتطبيق الضار تسجيل فلاتر الأهداف لقراءة علامات NFC أو الأجهزة المزوّدة بتقنية NFC. يمكن أن تُنشئ هذه الفلاتر فلاتر أخرى مشابهة للفلاتر التي يحدّدها تطبيق شرعي، ما يتيح للمهاجم قراءة محتوى بيانات NFC المتبادلة. تجدر ملاحظة أنه عندما يحدد نشاطان فلاتر الأهداف نفسها لعلامة NFC محدّدة، يكون مُحدِّد النشاط المعروض، وبالتالي لا يزال المستخدم بحاجة إلى اختيار الملفات تطبيقك لنجاح الهجوم. ومع ذلك، لا يزال هذا السيناريو ممكنًا عند دمج فلاتر الأهداف مع إخفاء البيانات. لا يكون هذا الهجوم مهمًا إلا في الحالات التي يمكن فيها اعتبار البيانات المتبادلة عبر NFC حساسة للغاية.
إجراءات التخفيف
فعند تطبيق إمكانيات القراءة عبر NFC في أحد التطبيقات، تعمل فلاتر الأهداف مع سجلات تطبيقات Android (AAR). يتيح تضمين سجل AAR داخل رسالة NDEF سيمنح تأكيدًا قويًا بأن تم بدء التطبيق الشرعي ونشاط معالجة NDEF المرتبط به. سيؤدي ذلك إلى منع التطبيقات أو الأنشطة غير المرغوب فيها من قراءة بيانات العلامات أو الأجهزة التي تتسم بدرجة عالية من الحساسية والتي يتم تبادلها من خلال تقنية NFC.
خطر: NFC - نقص التحقق من رسالة NDEF
عندما يتلقّى جهاز Android بيانات من علامة NFC أو جهاز مزوّد بتقنية NFC، يشغّل النظام تلقائيًا التطبيق أو النشاط المُحدَّد الذي تم ضبطه لمعالجة رسالة NDEF المضمّنة. وفقًا للمنطق المنفذ في التطبيق، تكون البيانات الموجودة في أو التي يتم استلامها من الجهاز، يمكن عرضها في أنشطة أخرى لتشغيل المزيد من الإجراءات، مثل فتح صفحات الويب.
قد يتيح التطبيق الذي لا يفتقر إلى التحقق من محتوى رسالة NDEF للمهاجمين استخدام أجهزة تمكّن تكنولوجيا NFC أو علامات NFC لإدخال حمولات بيانات ضارة في تطبيق، مما يتسبب في سلوك غير متوقع قد يؤدي إلى ملف ضار التنزيل أو إدخال الأمر أو DoS.
إجراءات التخفيف
قبل إرسال رسالة NDEF المستلَمة إلى أي مكوّن آخر من التطبيق، يجب التحقّق من صحة البيانات الواردة فيها للتأكّد من أنّها بالشكل المتوقّع وأنّها تحتوي على المعلومات المتوقّعة. يحول هذا دون تمرير البيانات الضارة إلى التطبيقات" من المكونات التي لم تتم تصفيتها، مما يقلل من خطر السلوك غير المتوقع أو هجمات إلكترونية باستخدام بيانات NFC التي تم التلاعب بها.
يعرض المقتطف التالي مثالاً على منطق التحقّق من صحة البيانات الذي تم تنفيذه كأحد methods باستخدام رسالة NDEF كوسيطة وفهرسها في صفيف الرسائل. تم تنفيذ ذلك من خلال مثال مطوّري تطبيقات Android للحصول على البيانات من علامة NDEF NFC التي تم مسحها ضوئيًا:
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;
}
المراجع
- أذونات وقت التشغيل
- أدلة الاتصال
- مثال
- BulkTransfer
- التشفير
- إعداد البلوتوث
- أساسيات NFC
- سجلات تطبيقات Android
- مواصفات البلوتوث الكلاسيكي