안전하지 않은 기계 대 기계 통신 설정

OWASP 카테고리: MASVS-CODE: 코드 품질

개요

사용자가 무선 주파수(RF) 통신 또는 케이블 연결을 사용하여 데이터를 전송하거나 다른 기기와 상호작용할 수 있는 기능을 구현하는 애플리케이션은 드물지 않습니다. 생성형 AI에 사용되는 가장 일반적인 이를 위한 Android는 클래식 블루투스(Bluetooth BR/EDR), Bluetooth Low(낮음)입니다. 에너지 (BLE), Wi-Fi P2P, NFC, USB

이러한 테크는 일반적으로 스마트 홈 액세서리, 건강 모니터링 기기, 공공장소와 통신합니다. 교통 키오스크, 결제 단말기 및 기타 Android 구동 장치를 지원합니다.

다른 채널과 마찬가지로, M2M 통신은 두 개 이상의 네트워크 사이에 설정된 신뢰 경계를 침해하려는 것을 목표로 하는 공격 더 많은 기기를 사용할 수 있습니다. 기기 명의 도용과 같은 기법은 악의적인 사용자가 통신 채널에 대한 광범위한 공격을 수행하는 데 활용될 수 있습니다.

Android는 기계 대 기계 구성을 위한 특정 API를 만듭니다. 커뮤니케이션에 사용할 수 있는 도구를 제공합니다

커뮤니케이션을 구현하는 동안 이러한 API는 오류로 인해 주의해서 사용해야 합니다. 프로토콜을 사용하면 사용자 또는 기기 데이터가 승인되지 않은 데이터에 노출될 수 있습니다. 있습니다. 최악의 경우 공격자가 하나 이상의 기기를 원격으로 탈취하여 기기의 콘텐츠에 대한 전체 액세스 권한을 얻을 수 있습니다.

영향

영향은 애플리케이션에 구현된 기기 간 기술에 따라 다를 수 있습니다.

M2M의 잘못된 사용 또는 구성 통신 채널에 의해 사용자 기기가 신뢰할 수 없는 있습니다. 이로 인해 기기가 중간자(MiTM), 명령어 삽입, DoS, 명의 도용 공격과 같은 추가 공격에 취약해질 수 있습니다.

위험: 무선 채널을 통한 민감한 정보 도청

M2M 통신 메커니즘을 구현할 때는 사용된 기술과 데이터 유형을 모두 고려해야 합니다 전송됩니다. 이러한 작업에는 관련 기기 간의 물리적 연결이 필요하므로 실제로는 케이블 연결이 더 안전하지만 기존 블루투스, BLE, NFC, Wi-Fi P2P와 같은 무선 주파수를 사용하는 통신 프로토콜은 가로챌 수 있습니다. 공격자가 데이터 교환에 참여하는 터미널 또는 액세스 포인트 중 하나를 가장하여 무선 통신을 가로채 민감한 사용자 데이터에 액세스할 수 있습니다. 또한 기기에 설치된 악성 애플리케이션이 통신 관련 런타임 권한을 부여받은 경우 시스템 메시지 버퍼를 읽어서 기기 간에 교환된 데이터를 가져올 수 있습니다.

완화 조치

애플리케이션에서 무선 채널을 통해 기기 간 민감한 정보 교환이 필요한 경우 암호화와 같은 애플리케이션 계층 보안 솔루션을 애플리케이션 코드에 구현해야 합니다. 이렇게 하면 공격자가 통신 채널을 스니핑하고 교환된 데이터를 일반 텍스트로 검색할 수 없습니다. 추가 리소스는 암호화 문서


위험: 무선 악성 데이터 삽입

무선 기기 간 통신 채널(기존 블루투스, BLE, NFC, Wi-Fi P2P)은 악성 데이터를 사용하여 조작될 수 있습니다. 충분히 숙련된 공격자는 사용 중인 통신 프로토콜을 식별하고 데이터 교환 흐름을 조작할 수 있습니다(예: 엔드포인트 중 하나를 명의 도용하여 특별히 제작된 페이로드를 전송). 이러한 종류의 악성 트래픽은 애플리케이션의 기능을 저하시킬 수 있으며 최악의 경우 예기치 않은 애플리케이션 및 기기 동작을 일으키거나 DoS, 명령어 삽입, 기기 탈취와 같은 공격을 초래할 수 있습니다.

완화 조치

Android는 개발자에게 관리할 수 있는 강력한 API를 제공합니다. 기계 대 기계 통신(예: 클래식 블루투스, BLE, NFC, Wi-Fi) P2P 세심하게 구현된 데이터 유효성 검사 로직과 결합해야 합니다. 두 기기 간에 교환되는 데이터를 정리합니다.

이 솔루션은 애플리케이션 수준에서 구현해야 하며 데이터가 예상 길이, 형식인지, 애플리케이션에서 해석할 수 있는 유효한 페이로드가 포함되어 있는지 확인하는 검사를 포함해야 합니다.

다음 스니펫은 데이터 유효성 검사 로직의 예를 보여줍니다. 이는 블루투스 데이터 전송을 구현하기 위한 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
        }
    }
}

자바

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 패킷 수를 제한하는 것이 좋습니다. 이렇게 하면 악성 기기가 러버덕과 같은 공격을 실행하지 못합니다.

이 유효성 검사는 버퍼 콘텐츠(예: 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()
    }
}

자바

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를 0으로 설정하면 애플리케이션이 백그라운드 또는 포그라운드에서 실행되는 한 기기를 검색할 수 있게 됩니다. 기존 블루투스 사양의 경우 검색 가능한 기기가 특정 탐색을 지속적으로 브로드캐스팅합니다. 메시지를 통해 다른 장치가 기기 데이터를 검색하거나 연결하도록 할 수 있습니다. 이러한 시나리오에서 악의적인 서드 파티는 이러한 메시지를 가로채 Android 지원 기기에 연결할 수 있습니다. 연결되면 공격자는 데이터 도용, DoS, 명령어 삽입과 같은 추가 공격을 실행할 수 있습니다.

완화 조치

EXTRA_DISCOVERABLE_DURATION를 0으로 설정하면 안 됩니다. EXTRA_DISCOVERABLE_DURATION 매개변수가 설정되지 않으면 기본적으로 Android는 2분 동안 기기를 검색 가능하게 만듭니다. 이 EXTRA_DISCOVERABLE_DURATION 매개변수는 2시간 (7,200초)입니다. 그것은 검색 가능한 기간을 가장 짧은 시간으로 유지하는 것이 좋습니다. 애플리케이션 사용 사례에 따라 여러 옵션을 선택할 수 있습니다


위험: NFC – 클론된 인텐트 필터

악성 애플리케이션이 인텐트 필터를 등록하여 특정 NFC 태그를 읽거나 NFC 지원 기기 이러한 필터는 공격자가 콘텐츠를 읽을 수 있게 만드는 교환된 NFC 데이터입니다. 두 개의 활동이 같은 인텐트 필터에 관해서는 활동 선택기가 따라서 사용자는 여전히 악성 악성 링크를 도움이 될 수 있습니다 하지만 인텐트 필터를 클로킹과 결합하면 이 시나리오는 여전히 가능합니다. 이 공격은 NFC를 통해 교환되는 데이터가 매우 민감하다고 간주될 수 있는 경우에만 중요합니다.

완화 조치

애플리케이션 내에서 NFC 읽기 기능을 구현할 때 인텐트 필터는 Android 애플리케이션 레코드 (AAR)와 함께 사용할 수 있습니다. 임베딩 NDEF 메시지 내의 AAR 레코드는 합법적인 애플리케이션과 관련 NDEF 처리 활동이 시작됩니다. 이렇게 하면 원치 않는 애플리케이션이나 활동이 NFC를 통해 교환되는 민감한 태그 또는 기기 데이터


위험: NFC – NDEF 메시지 유효성 검사 부족

Android 지원 기기가 NFC 태그 또는 NFC 지원 기기에서 데이터를 수신하면 시스템은 내에 포함된 NDEF 메시지를 처리하도록 구성된 애플리케이션 또는 특정 활동을 자동으로 트리거합니다. 애플리케이션에 구현된 로직에 따라 태그가 실행되거나 기기로부터 수신되어 추가 작업(예: 웹페이지 열기)

NDEF 메시지 콘텐츠 유효성 검사가 없는 애플리케이션은 공격자가 NFC 지원 기기 또는 NFC 태그를 사용하여 애플리케이션 내에 악성 페이로드를 삽입할 수 있게 허용하여 악성 파일 다운로드, 명령어 삽입 또는 DoS로 이어질 수 있는 예상치 못한 동작을 일으킬 수 있습니다.

완화 조치

수신된 NDEF 메시지를 다른 애플리케이션 구성 요소에 디스패치하기 전에 내의 데이터는 예상된 형식이고 확인할 수 있습니다 이렇게 하면 악성 데이터가 필터링되지 않은 상태로 다른 애플리케이션 구성요소에 전달되지 않아 조작된 NFC 데이터를 사용한 예기치 않은 동작이나 공격의 위험이 줄어듭니다.

다음 스니펫은 NDEF 메시지를 인수로 사용하는 메서드로 구현된 데이터 유효성 검사 로직의 예와 메시지 배열의 색인을 보여줍니다. 이는 스캔된 NFC NDEF 태그에서 데이터를 가져오기 위해 Android 개발자 를 통해 구현되었습니다.

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

리소스