تنظیم ارتباط ماشین به ماشین ناامن

رده OWASP: MASVS-CODE: کیفیت کد

نمای کلی

دیدن برنامه‌هایی که به کاربران امکان انتقال داده یا تعامل با سایر دستگاه‌ها را با استفاده از ارتباطات فرکانس رادیویی (RF) یا اتصالات کابلی می‌دهند، نادر نیست. رایج‌ترین فناوری‌های مورد استفاده در اندروید برای این منظور عبارتند از بلوتوث کلاسیک (بلوتوث BR/EDR)، بلوتوث کم‌مصرف (BLE)، وای‌فای P2P، NFC و USB.

این فناوری‌ها معمولاً در برنامه‌هایی پیاده‌سازی می‌شوند که انتظار می‌رود با لوازم جانبی خانه هوشمند، دستگاه‌های نظارت بر سلامت، کیوسک‌های حمل و نقل عمومی، پایانه‌های پرداخت و سایر دستگاه‌های مبتنی بر اندروید ارتباط برقرار کنند.

همانند هر کانال ارتباطی دیگر، ارتباطات ماشین به ماشین نیز مستعد حملاتی هستند که هدفشان به خطر انداختن مرز اعتماد ایجاد شده بین دو یا چند دستگاه است. تکنیک‌هایی مانند جعل هویت دستگاه می‌تواند توسط کاربران مخرب برای دستیابی به تعداد زیادی حمله علیه کانال ارتباطی مورد استفاده قرار گیرد.

اندروید APIهای خاصی را برای پیکربندی ارتباطات ماشین به ماشین در اختیار توسعه‌دهندگان قرار می‌دهد.

این APIها باید با دقت مورد استفاده قرار گیرند زیرا خطاها هنگام پیاده‌سازی پروتکل‌های ارتباطی ممکن است منجر به افشای داده‌های کاربر یا دستگاه به اشخاص ثالث غیرمجاز شود. در بدترین حالت، مهاجمان ممکن است بتوانند از راه دور یک یا چند دستگاه را در اختیار بگیرند و در نتیجه به محتوای دستگاه دسترسی کامل پیدا کنند.

تأثیر

این تأثیر ممکن است بسته به فناوری ارتباط دستگاه به دستگاه پیاده‌سازی شده در برنامه، متفاوت باشد.

استفاده یا پیکربندی نادرست کانال‌های ارتباطی ماشین به ماشین ممکن است دستگاه کاربر را در معرض تلاش‌های ارتباطی غیرقابل اعتماد قرار دهد. این امر می‌تواند منجر به آسیب‌پذیری دستگاه در برابر حملات دیگری مانند حمله مرد میانی (MiTM)، تزریق دستور، حملات DoS یا جعل هویت شود.

خطر: استراق سمع داده‌های حساس از طریق کانال‌های بی‌سیم

هنگام پیاده‌سازی مکانیسم‌های ارتباط ماشین به ماشین، باید به فناوری مورد استفاده و نوع داده‌هایی که باید منتقل شوند، توجه دقیقی شود. اگرچه اتصالات کابلی در عمل برای چنین کارهایی امن‌تر هستند، زیرا به یک پیوند فیزیکی بین دستگاه‌های درگیر نیاز دارند، اما پروتکل‌های ارتباطی با استفاده از فرکانس‌های رادیویی مانند بلوتوث کلاسیک، BLE، NFC و Wifi P2P می‌توانند رهگیری شوند. یک مهاجم ممکن است بتواند یکی از ترمینال‌ها یا نقاط دسترسی درگیر در تبادل داده‌ها را جعل هویت کند، ارتباط را از طریق هوا رهگیری کند و در نتیجه به داده‌های حساس کاربر دسترسی پیدا کند. علاوه بر این، برنامه‌های مخرب نصب شده روی دستگاه، در صورت اعطای مجوزهای زمان اجرای خاص ارتباط، ممکن است بتوانند با خواندن بافرهای پیام سیستم، داده‌های رد و بدل شده بین دستگاه‌ها را بازیابی کنند.

کاهش‌ها

If the application does require machine-to-machine exchange of sensitive data over wireless channels, then application-layer security solutions, such as encryption, should be implemented in the application's code. This will prevent attackers from sniffing on the communication channel and retrieving the exchanged data in clear-text. For additional resources, refer to the Cryptography documentation.


خطر: تزریق داده‌های مخرب بی‌سیم

کانال‌های ارتباطی بی‌سیم ماشین به ماشین (بلوتوث کلاسیک، BLE، NFC، Wifi P2P) را می‌توان با استفاده از داده‌های مخرب دستکاری کرد. مهاجمان ماهر می‌توانند پروتکل ارتباطی مورد استفاده را شناسایی کرده و جریان تبادل داده‌ها را دستکاری کنند، به عنوان مثال با جعل هویت یکی از نقاط پایانی و ارسال بارهای مخرب خاص. این نوع ترافیک مخرب ممکن است عملکرد برنامه را مختل کند و در بدترین حالت، باعث رفتار غیرمنتظره برنامه و دستگاه شود یا منجر به حملاتی مانند DoS، تزریق دستور یا تصاحب دستگاه شود.

کاهش‌ها

اندروید APIهای قدرتمندی را برای مدیریت ارتباطات ماشین به ماشین مانند بلوتوث کلاسیک، BLE، NFC و Wifi P2P در اختیار توسعه‌دهندگان قرار می‌دهد. این موارد باید با یک منطق اعتبارسنجی داده که به دقت پیاده‌سازی شده است، ترکیب شوند تا هرگونه داده‌ای که بین دو دستگاه رد و بدل می‌شود، ایمن‌سازی شود.

این راهکار باید در سطح برنامه کاربردی پیاده‌سازی شود و شامل بررسی‌هایی باشد که تأیید کند آیا داده‌ها طول و قالب مورد انتظار را دارند و حاوی محتوای معتبری هستند که توسط برنامه کاربردی قابل تفسیر باشد یا خیر.

قطعه کد زیر نمونه‌ای از منطق اعتبارسنجی داده‌ها را نشان می‌دهد. این کد بر روی مثال توسعه‌دهندگان اندروید برای پیاده‌سازی انتقال داده از طریق بلوتوث پیاده‌سازی شده است:

کاتلین

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
        }
    }
}

جاوا

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 غیرقابل اعتمادی هستند که عمداً یا سهواً به دستگاه متصل می‌شوند.

اگر برنامه با استفاده از PID/VID دستگاه‌های USB را برای فعال کردن عملکردهای خاص درون برنامه‌ای فیلتر کند، مهاجمان ممکن است بتوانند با جعل هویت دستگاه قانونی، داده‌های ارسالی از طریق کانال USB را دستکاری کنند. حملاتی از این نوع می‌توانند به کاربران مخرب اجازه دهند کلیدهای فشرده شده را به دستگاه ارسال کنند یا فعالیت‌هایی را در برنامه اجرا کنند که در بدترین حالت، ممکن است منجر به اجرای کد از راه دور یا دانلود نرم‌افزار ناخواسته شود.

کاهش‌ها

یک منطق اعتبارسنجی در سطح برنامه باید پیاده‌سازی شود. این منطق باید داده‌های ارسالی از طریق USB را فیلتر کند تا مطمئن شود طول، قالب و محتوا با مورد استفاده برنامه مطابقت دارند. به عنوان مثال، یک مانیتور ضربان قلب نباید بتواند دستورات ضربه کلید را ارسال کند.

علاوه بر این، در صورت امکان، باید به محدود کردن تعداد بسته‌های USB که برنامه می‌تواند از دستگاه USB دریافت کند، توجه شود. این کار از انجام حملاتی مانند rubber ducky توسط دستگاه‌های مخرب جلوگیری می‌کند.

این اعتبارسنجی را می‌توان با ایجاد یک نخ جدید برای بررسی محتوای بافر، مثلاً روی یک bulkTransfer ، انجام داد:

کاتلین

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()
    }
}

جاوا

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 کاهش یافته‌اند و برای تکمیل مطلب در اینجا آورده شده‌اند.

خطر: بلوتوث - زمان کشف نادرست

همانطور که در مستندات بلوتوث توسعه‌دهندگان اندروید برجسته شده است، هنگام پیکربندی رابط بلوتوث در داخل برنامه، استفاده از متد startActivityForResult(Intent, int) برای فعال کردن قابلیت کشف دستگاه و تنظیم EXTRA_DISCOVERABLE_DURATION روی صفر باعث می‌شود دستگاه تا زمانی که برنامه در پس‌زمینه یا پیش‌زمینه اجرا می‌شود، قابل کشف باشد. در مورد مشخصات کلاسیک بلوتوث ، دستگاه‌های قابل کشف دائماً پیام‌های کشف خاصی را پخش می‌کنند که به سایر دستگاه‌ها اجازه می‌دهد داده‌های دستگاه را بازیابی کنند یا به آن متصل شوند. در چنین سناریویی، یک شخص ثالث مخرب می‌تواند چنین پیام‌هایی را رهگیری کرده و به دستگاه مبتنی بر اندروید متصل شود. پس از اتصال، مهاجم می‌تواند حملات بیشتری مانند سرقت داده‌ها، DoS یا تزریق دستور را انجام دهد.

کاهش‌ها

مقدار EXTRA_DISCOVERABLE_DURATION هرگز نباید روی صفر تنظیم شود. اگر پارامتر EXTRA_DISCOVERABLE_DURATION تنظیم نشود، اندروید به طور پیش‌فرض دستگاه‌ها را به مدت ۲ دقیقه قابل شناسایی می‌کند. حداکثر مقداری که می‌توان برای پارامتر EXTRA_DISCOVERABLE_DURATION تنظیم کرد ۲ ساعت (۷۲۰۰ ثانیه) است. توصیه می‌شود مدت زمان قابل شناسایی را بسته به مورد استفاده برنامه، در کمترین زمان ممکن نگه دارید.


خطر: NFC – فیلترهای هدف شبیه‌سازی‌شده

یک برنامه مخرب می‌تواند فیلترهای intent- را برای خواندن برچسب‌های NFC خاص یا دستگاه‌های دارای قابلیت NFC ثبت کند. این فیلترها می‌توانند فیلترهای تعریف شده توسط یک برنامه قانونی را کپی کنند و به مهاجم این امکان را می‌دهند که محتوای داده‌های NFC رد و بدل شده را بخواند. لازم به ذکر است که وقتی دو فعالیت، فیلترهای intent- یکسانی را برای یک برچسب NFC خاص مشخص می‌کنند، Activity Chooser نمایش داده می‌شود، بنابراین کاربر همچنان باید برنامه مخرب را برای موفقیت حمله انتخاب کند. با این وجود، با ترکیب فیلترهای intent- با پنهان‌سازی، این سناریو همچنان امکان‌پذیر است. این حمله فقط برای مواردی قابل توجه است که داده‌های رد و بدل شده از طریق NFC بسیار حساس تلقی شوند.

کاهش‌ها

هنگام پیاده‌سازی قابلیت‌های خواندن NFC در یک برنامه، می‌توان از فیلترهای intent به همراه رکوردهای برنامه اندروید (AAR) استفاده کرد. جاسازی رکورد AAR در یک پیام NDEF، تضمین قوی می‌دهد که فقط برنامه قانونی و فعالیت مدیریت NDEF مرتبط با آن، شروع شده است. این امر مانع از خواندن داده‌های بسیار حساس برچسب یا دستگاه که از طریق NFC رد و بدل می‌شوند، توسط برنامه‌ها یا فعالیت‌های ناخواسته می‌شود.


خطر: NFC - عدم اعتبارسنجی پیام NDEF

وقتی یک دستگاه اندروید از یک تگ NFC یا دستگاه دارای NFC داده دریافت می‌کند، سیستم به طور خودکار برنامه یا فعالیت خاصی را که برای مدیریت پیام NDEF موجود در آن پیکربندی شده است، فعال می‌کند. طبق منطق پیاده‌سازی شده در برنامه، داده‌های موجود در تگ یا داده‌های دریافتی از دستگاه می‌توانند برای انجام اقدامات بیشتر، مانند باز کردن صفحات وب، به سایر فعالیت‌ها ارسال شوند.

یک برنامه‌ی کاربردی که فاقد اعتبارسنجی محتوای پیام NDEF باشد، ممکن است به مهاجمان اجازه دهد تا از دستگاه‌های دارای NFC یا برچسب‌های NFC برای تزریق بارهای مخرب در برنامه استفاده کنند و باعث رفتار غیرمنتظره‌ای شوند که ممکن است منجر به دانلود فایل مخرب، تزریق دستور یا DoS شود.

کاهش‌ها

قبل از ارسال پیام NDEF دریافتی به هر جزء برنامه دیگر، داده‌های درون آن باید اعتبارسنجی شوند تا در قالب مورد انتظار باشند و حاوی اطلاعات مورد انتظار باشند. این کار از انتقال داده‌های مخرب به اجزای برنامه‌های دیگر به صورت فیلتر نشده جلوگیری می‌کند و خطر رفتار یا حملات غیرمنتظره با استفاده از داده‌های NFC دستکاری شده را کاهش می‌دهد.

قطعه کد زیر، نمونه‌ای از منطق اعتبارسنجی داده‌ها را نشان می‌دهد که به عنوان یک متد با یک پیام NDEF به عنوان آرگومان و اندیس آن در آرایه messages پیاده‌سازی شده است. این کد بر روی مثال توسعه‌دهندگان اندروید برای دریافت داده‌ها از یک تگ NDEF NFC اسکن شده پیاده‌سازی شده است:

کاتلین

//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
}

جاوا

//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;
    }

منابع