Thiết lập giao tiếp không an toàn giữa máy với máy

Danh mục OWASP: MASVS-CODE: Chất lượng mã

Tổng quan

Không hiếm khi thấy các ứng dụng triển khai chức năng cho phép người dùng truyền dữ liệu hoặc tương tác với các thiết bị khác bằng cách sử dụng thông tin liên lạc RF (tần số vô tuyến) hoặc kết nối có dây. Các công nghệ phổ biến nhất được dùng trong Android cho mục đích này là Bluetooth cổ điển (Bluetooth BR/EDR), Bluetooth năng lượng thấp (BLE), Wifi P2P, NFC và USB.

Những công nghệ này thường được triển khai trong các ứng dụng dự kiến sẽ giao tiếp với phụ kiện nhà thông minh, thiết bị theo dõi sức khoẻ, ki-ốt giao thông công cộng, thiết bị thanh toán và các thiết bị khác chạy Android.

Giống như mọi kênh khác, hoạt động giao tiếp giữa các máy cũng dễ bị tấn công nhằm xâm phạm ranh giới tin cậy được thiết lập giữa hai hoặc nhiều thiết bị. Những người dùng có ý đồ xấu có thể tận dụng các kỹ thuật như mạo danh thiết bị để thực hiện nhiều cuộc tấn công vào kênh liên lạc.

Android cung cấp các API cụ thể để nhà phát triển có thể định cấu hình hoạt động giao tiếp giữa các máy.

Bạn nên sử dụng các API này một cách cẩn thận vì lỗi trong quá trình triển khai giao thức giao tiếp có thể khiến dữ liệu người dùng hoặc dữ liệu trên thiết bị bị lộ cho các bên thứ ba không được phép. Trong trường hợp xấu nhất, kẻ tấn công có thể kiểm soát từ xa một hoặc nhiều thiết bị, do đó có toàn quyền truy cập vào nội dung trên thiết bị.

Tác động

Mức độ tác động có thể khác nhau tuỳ theo công nghệ giữa các thiết bị được triển khai trong ứng dụng.

Việc sử dụng hoặc định cấu hình sai các kênh giao tiếp giữa các máy có thể khiến thiết bị của người dùng bị lộ trước các nỗ lực giao tiếp không đáng tin cậy. Điều này có thể khiến thiết bị dễ bị tấn công hơn, chẳng hạn như tấn công xen giữa (MiTM), chèn lệnh, DoS hoặc tấn công giả mạo.

Rủi ro: Nghe lén dữ liệu nhạy cảm qua các kênh không dây

Khi triển khai cơ chế giao tiếp giữa các máy, bạn cần cân nhắc kỹ lưỡng cả công nghệ được sử dụng và loại dữ liệu cần truyền. Mặc dù các kết nối có dây trên thực tế an toàn hơn cho những tác vụ như vậy, vì chúng yêu cầu một đường liên kết vật lý giữa các thiết bị liên quan, nhưng các giao thức giao tiếp sử dụng tần số vô tuyến như Bluetooth cổ điển, BLE, NFC và Wifi P2P có thể bị chặn. Kẻ tấn công có thể mạo danh một trong các thiết bị đầu cuối hoặc điểm truy cập liên quan đến việc trao đổi dữ liệu, chặn thông tin liên lạc qua mạng, do đó có quyền truy cập vào dữ liệu nhạy cảm của người dùng. Ngoài ra, nếu được cấp quyền khi bắt đầu chạy dành riêng cho hoạt động giao tiếp, thì các ứng dụng độc hại được cài đặt trên thiết bị có thể truy xuất dữ liệu được trao đổi giữa các thiết bị bằng cách đọc bộ đệm thông báo hệ thống.

Giải pháp giảm thiểu

Nếu ứng dụng yêu cầu trao đổi dữ liệu nhạy cảm giữa các máy qua các kênh không dây, thì các giải pháp bảo mật ở lớp ứng dụng (chẳng hạn như mã hoá) phải được triển khai trong mã của ứng dụng. Điều này sẽ ngăn chặn kẻ tấn công theo dõi kênh giao tiếp và truy xuất dữ liệu đã trao đổi ở dạng văn bản thô. Để biết thêm tài nguyên, hãy tham khảo tài liệu Mật mã học.


Rủi ro: Tiêm dữ liệu độc hại qua mạng không dây

Các kênh giao tiếp không dây giữa các máy (Bluetooth cổ điển, BLE, NFC, Wifi P2P) có thể bị giả mạo bằng dữ liệu độc hại. Những kẻ tấn công có đủ kỹ năng có thể xác định giao thức giao tiếp đang được sử dụng và can thiệp vào luồng trao đổi dữ liệu, chẳng hạn như mạo danh một trong các điểm cuối, gửi tải trọng được tạo đặc biệt. Loại lưu lượng truy cập độc hại này có thể làm giảm chức năng của ứng dụng và trong trường hợp xấu nhất, gây ra hành vi không mong muốn của ứng dụng và thiết bị, hoặc dẫn đến các cuộc tấn công như DoS, chèn lệnh hoặc chiếm đoạt thiết bị.

Giải pháp giảm thiểu

Android cung cấp cho nhà phát triển các API mạnh mẽ để quản lý hoạt động giao tiếp giữa các máy, chẳng hạn như Bluetooth cổ điển, BLE, NFC và Wifi P2P. Bạn nên kết hợp những biện pháp này với một logic xác thực dữ liệu được triển khai cẩn thận để làm sạch mọi dữ liệu được trao đổi giữa hai thiết bị.

Giải pháp này phải được triển khai ở cấp ứng dụng và phải bao gồm các bước kiểm tra để xác minh xem dữ liệu có độ dài, định dạng như mong đợi hay không và có chứa tải trọng hợp lệ mà ứng dụng có thể diễn giải hay không.

Đoạn mã sau đây cho thấy logic xác thực dữ liệu mẫu. Điều này được triển khai thông qua ví dụ của nhà phát triển Android để triển khai tính năng chuyển dữ liệu qua 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;
           }
    }

Rủi ro: Dữ liệu độc hại được chèn qua USB

Kết nối USB giữa hai thiết bị có thể là mục tiêu của người dùng độc hại muốn chặn thông tin liên lạc. Trong trường hợp này, đường liên kết vật lý bắt buộc tạo thành một lớp bảo mật bổ sung vì kẻ tấn công cần có quyền truy cập vào cáp kết nối các thiết bị đầu cuối để có thể nghe lén mọi thông báo. Một vectơ tấn công khác là các thiết bị USB không đáng tin cậy được cắm vào thiết bị, dù là vô tình hay cố ý.

Nếu ứng dụng lọc các thiết bị USB bằng PID/VID để kích hoạt chức năng cụ thể trong ứng dụng, thì kẻ tấn công có thể giả mạo thiết bị hợp pháp để can thiệp vào dữ liệu được gửi qua kênh USB. Các cuộc tấn công kiểu này có thể cho phép người dùng độc hại gửi các lần nhấn phím đến thiết bị hoặc thực thi các hoạt động của ứng dụng. Trong trường hợp xấu nhất, điều này có thể dẫn đến việc thực thi mã từ xa hoặc tải phần mềm không mong muốn xuống.

Giải pháp giảm thiểu

Bạn nên triển khai logic xác thực ở cấp ứng dụng. Logic này sẽ lọc dữ liệu được gửi qua USB, kiểm tra để đảm bảo độ dài, định dạng và nội dung khớp với trường hợp sử dụng ứng dụng. Ví dụ: một thiết bị theo dõi nhịp tim không được phép gửi lệnh nhấn phím.

Ngoài ra, khi có thể, bạn nên cân nhắc việc hạn chế số lượng gói USB mà ứng dụng có thể nhận được từ thiết bị USB. Điều này ngăn chặn các thiết bị độc hại thực hiện các cuộc tấn công như cuộc tấn công bằng thiết bị giả dạng ổ USB.

Bạn có thể thực hiện quy trình xác thực này bằng cách tạo một luồng mới để kiểm tra nội dung trong vùng đệm, ví dụ: khi có 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();
        }
    }

Rủi ro cụ thể

Phần này tổng hợp các rủi ro đòi hỏi chiến lược giảm thiểu không theo chuẩn hoặc được giảm thiểu ở một số cấp độ SDK nhất định và được liệt kê ở đây chỉ để cho đủ.

Rủi ro: Bluetooth – thời gian hiển thị không chính xác

Như được nêu bật trong tài liệu về Bluetooth dành cho nhà phát triển Android, trong khi định cấu hình giao diện Bluetooth trong ứng dụng, việc sử dụng phương thức startActivityForResult(Intent, int) để bật khả năng phát hiện thiết bị và đặt EXTRA_DISCOVERABLE_DURATION thành 0 sẽ khiến thiết bị có thể phát hiện được miễn là ứng dụng đang chạy ở nền sau hoặc nền trước. Đối với quy cách Bluetooth truyền thống, các thiết bị có thể phát hiện liên tục phát các thông báo phát hiện cụ thể cho phép các thiết bị khác truy xuất dữ liệu trên thiết bị hoặc kết nối với thiết bị đó. Trong trường hợp như vậy, bên thứ ba độc hại có thể chặn các thông báo đó và kết nối với thiết bị chạy Android. Sau khi kết nối, kẻ tấn công có thể thực hiện các cuộc tấn công khác như đánh cắp dữ liệu, DoS hoặc tiêm lệnh.

Giải pháp giảm thiểu

Bạn không bao giờ được đặt EXTRA_DISCOVERABLE_DURATION thành 0. Nếu bạn không đặt tham số EXTRA_DISCOVERABLE_DURATION, theo mặc định, Android sẽ cho phép các thiết bị tìm thấy nhau trong 2 phút. Giá trị tối đa có thể đặt cho tham số EXTRA_DISCOVERABLE_DURATION là 2 giờ (7200 giây). Bạn nên giữ thời gian có thể phát hiện ở mức ngắn nhất có thể, tuỳ theo trường hợp sử dụng ứng dụng.


Rủi ro: NFC – bộ lọc ý định được sao chép

Một ứng dụng độc hại có thể đăng ký bộ lọc ý định để đọc các thẻ NFC cụ thể hoặc các thiết bị có NFC. Các bộ lọc này có thể sao chép những bộ lọc do một ứng dụng hợp pháp xác định, giúp kẻ tấn công có thể đọc nội dung của dữ liệu NFC được trao đổi. Xin lưu ý rằng khi hai hoạt động chỉ định cùng một bộ lọc ý định cho một thẻ NFC cụ thể, Trình chọn hoạt động sẽ xuất hiện. Do đó, người dùng vẫn cần chọn ứng dụng độc hại để cuộc tấn công thành công. Tuy nhiên, nếu kết hợp bộ lọc ý định với kỹ thuật che giấu, thì trường hợp này vẫn có thể xảy ra. Cuộc tấn công này chỉ đáng kể trong trường hợp dữ liệu được trao đổi qua NFC có thể được coi là rất nhạy cảm.

Giải pháp giảm thiểu

Khi triển khai các chức năng đọc NFC trong một ứng dụng, bạn có thể dùng bộ lọc ý định cùng với bản ghi ứng dụng Android (AAR). Việc nhúng bản ghi AAR vào trong một thông báo NDEF sẽ đảm bảo chắc chắn rằng chỉ ứng dụng hợp lệ và hoạt động xử lý NDEF được liên kết của ứng dụng đó mới được khởi động. Điều này sẽ ngăn các ứng dụng hoặc hoạt động không mong muốn đọc thẻ có độ nhạy cao hoặc dữ liệu trên thiết bị được trao đổi qua NFC.


Rủi ro: NFC – thiếu quy trình xác thực thông báo NDEF

Khi một thiết bị chạy Android nhận được dữ liệu từ thẻ NFC hoặc thiết bị có NFC, hệ thống sẽ tự động kích hoạt ứng dụng hoặc hoạt động cụ thể được định cấu hình để xử lý thông báo NDEF có trong đó. Theo logic được triển khai trong ứng dụng, dữ liệu có trong thẻ hoặc nhận được từ thiết bị có thể được cung cấp cho các hoạt động khác để kích hoạt các hành động khác, chẳng hạn như mở trang web.

Một ứng dụng thiếu quy trình xác thực nội dung thông báo NDEF có thể cho phép kẻ tấn công sử dụng các thiết bị có hỗ trợ NFC hoặc thẻ NFC để chèn tải trọng độc hại vào ứng dụng, gây ra hành vi không mong muốn có thể dẫn đến việc tải tệp độc hại xuống, chèn lệnh hoặc tấn công từ chối dịch vụ (DoS).

Giải pháp giảm thiểu

Trước khi gửi thông báo NDEF nhận được đến bất kỳ thành phần của ứng dụng nào khác, dữ liệu trong thông báo đó phải được xác thực để có định dạng dự kiến và chứa thông tin dự kiến. Điều này giúp tránh việc dữ liệu độc hại được truyền đến các thành phần của ứng dụng khác mà không được lọc, giảm nguy cơ xảy ra hành vi không mong muốn hoặc các cuộc tấn công bằng cách sử dụng dữ liệu NFC giả mạo.

Đoạn mã sau đây cho thấy logic xác thực dữ liệu mẫu được triển khai dưới dạng một phương thức có thông báo NDEF làm đối số và chỉ mục của đối số đó trong mảng thông báo. Điều này được triển khai trên ví dụ của nhà phát triển Android để lấy dữ liệu từ thẻ NDEF NFC được quét:

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

Tài nguyên