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

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

نمای کلی

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

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

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

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

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

تاثیر

تأثیر ممکن است بسته به فناوری دستگاه به دستگاه اجرا شده در برنامه متفاوت باشد.

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

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

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

اقدامات کاهشی

اگر برنامه نیاز به تبادل ماشین به ماشین داده های حساس از طریق کانال های بی سیم دارد، راه حل های امنیتی لایه برنامه مانند رمزگذاری باید در کد برنامه پیاده سازی شود. این کار از مهاجمان جلوگیری می کند تا کانال ارتباطی را شنود کنند و داده های مبادله شده را به صورت متن واضح بازیابی کنند. برای منابع بیشتر، به مستندات رمزنگاری مراجعه کنید.


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

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

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

اقدامات کاهشی

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

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

این اعتبار سنجی را می توان با ایجاد یک رشته جدید برای بررسی محتوای بافر، به عنوان مثال، بر روی یک 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 تنظیم نشده باشد، به طور پیش‌فرض، Android دستگاه‌ها را به مدت ۲ دقیقه قابل شناسایی می‌کند. حداکثر مقدار قابل تنظیم برای پارامتر EXTRA_DISCOVERABLE_DURATION 2 ساعت (7200 ثانیه) است. توصیه می شود با توجه به موارد استفاده از برنامه، مدت زمان قابل کشف را در کمترین زمان ممکن نگه دارید.


خطر: NFC - فیلترهای هدف کلون شده

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

اقدامات کاهشی

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


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

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

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

اقدامات کاهشی

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

قطعه زیر به عنوان مثال منطق اعتبارسنجی داده را نشان می دهد که به عنوان یک متد با یک پیام 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;
    }

منابع