หมวดหมู่ OWASP: MASVS-CODE: คุณภาพของโค้ด
ภาพรวม
เรามักจะเห็นแอปพลิเคชันที่ใช้ฟังก์ชันที่อนุญาตให้ผู้ใช้โอนข้อมูลหรือโต้ตอบกับอุปกรณ์อื่นๆ โดยใช้การสื่อสารความถี่วิทยุ (RF) หรือการเชื่อมต่อแบบมีสาย เทคโนโลยีที่ใช้กันมากที่สุดใน Android เพื่อจุดประสงค์นี้คือบลูทูธแบบคลาสสิก (Bluetooth BR/EDR), บลูทูธพลังงานต่ำ (BLE), Wifi P2P, NFC และ USB
โดยปกติแล้ว เทคโนโลยีเหล่านี้จะนำไปใช้ในแอปพลิเคชันที่คาดว่าจะสื่อสารกับอุปกรณ์เสริมสมาร์ทโฮม อุปกรณ์ตรวจสอบสุขภาพ คีออสระบบขนส่งสาธารณะ เครื่องชำระเงิน และอุปกรณ์อื่นๆ ที่ใช้ Android
เช่นเดียวกับช่องทางอื่นๆ การสื่อสารระหว่างเครื่องก็อาจถูกโจมตีได้ ซึ่งมีเป้าหมายเพื่อทำลายขอบเขตความน่าเชื่อถือที่สร้างขึ้นระหว่างอุปกรณ์ 2 เครื่องขึ้นไป ผู้ใช้ที่เป็นอันตรายอาจใช้ประโยชน์จากเทคนิคต่างๆ เช่น การแอบอ้างเป็นอุปกรณ์ เพื่อทำการโจมตีจำนวนมากกับช่องทางการสื่อสาร
Android มี API เฉพาะสำหรับการกำหนดค่าการสื่อสารแบบเครื่องต่อเครื่อง ที่พร้อมให้บริการแก่นักพัฒนาแอป
คุณควรใช้ API เหล่านี้อย่างระมัดระวัง เนื่องจากข้อผิดพลาดขณะใช้โปรโตคอลการสื่อสาร อาจทำให้ข้อมูลผู้ใช้หรือข้อมูลอุปกรณ์ถูกเปิดเผยต่อบุคคลที่สามที่ไม่ได้รับอนุญาต ในกรณีที่แย่ที่สุด ผู้โจมตีอาจเข้าควบคุมอุปกรณ์อย่างน้อย 1 เครื่องจากระยะไกล และเข้าถึงเนื้อหาในอุปกรณ์ได้อย่างเต็มที่
ผลกระทบ
ผลกระทบอาจแตกต่างกันไปตามเทคโนโลยีแบบอุปกรณ์ต่ออุปกรณ์ที่ใช้ใน แอปพลิเคชัน
การใช้งานหรือการกำหนดค่าช่องทางการสื่อสารแบบเครื่องต่อเครื่องที่ไม่ถูกต้องอาจทำให้อุปกรณ์ของผู้ใช้เสี่ยงต่อความพยายามในการสื่อสารที่ไม่น่าเชื่อถือ ซึ่งอาจทำให้อุปกรณ์มีช่องโหว่ที่จะถูกโจมตีเพิ่มเติม เช่น การโจมตีแบบแทรกกลางการสื่อสาร (Man-In-The-Middle หรือ MITM), การแทรกคำสั่ง, การโจมตีแบบปฏิเสธการให้บริการ (DoS) หรือการโจมตีแบบแอบอ้างเป็นบุคคลอื่น
ความเสี่ยง: การดักฟังข้อมูลที่ละเอียดอ่อนผ่านช่องทางไร้สาย
เมื่อติดตั้งกลไกการสื่อสารแบบเครื่องต่อเครื่อง ควรพิจารณาอย่างรอบคอบทั้งเทคโนโลยีที่ใช้และประเภทข้อมูลที่ควรส่ง แม้ว่าการเชื่อมต่อแบบใช้สายจะปลอดภัยกว่าในทางปฏิบัติสำหรับงานดังกล่าว เนื่องจากต้องมีการเชื่อมต่อทางกายภาพระหว่างอุปกรณ์ที่เกี่ยวข้อง แต่โปรโตคอลการสื่อสารที่ใช้ความถี่วิทยุ เช่น บลูทูธคลาสสิก, BLE, NFC และ Wi-Fi P2P อาจถูกดักฟังได้ ผู้โจมตีอาจแอบอ้างเป็นหนึ่งในเทอร์มินัลหรือจุดเข้าถึงที่เกี่ยวข้องกับการแลกเปลี่ยนข้อมูล ดักฟังการสื่อสารผ่านอากาศ และเข้าถึงข้อมูลผู้ใช้ที่ละเอียดอ่อนได้ในที่สุด นอกจากนี้ แอปพลิเคชันที่เป็นอันตรายซึ่งติดตั้งในอุปกรณ์ หากได้รับสิทธิ์รันไทม์เฉพาะการสื่อสาร อาจดึงข้อมูลที่แลกเปลี่ยนระหว่างอุปกรณ์ได้โดยการอ่านบัฟเฟอร์ข้อความของระบบ
การลดปัญหา
หากแอปพลิเคชันจำเป็นต้องมีการแลกเปลี่ยนข้อมูลที่ละเอียดอ่อนแบบเครื่องต่อเครื่อง ผ่านช่องทางไร้สาย คุณควรใช้โซลูชันด้านความปลอดภัยที่เลเยอร์แอปพลิเคชัน เช่น การเข้ารหัส ในโค้ดของแอปพลิเคชัน ซึ่งจะป้องกันไม่ให้ ผู้โจมตีดักฟังช่องทางการสื่อสารและดึงข้อมูลที่ แลกเปลี่ยนเป็นข้อความธรรมดา ดูแหล่งข้อมูลเพิ่มเติมได้ในเอกสารประกอบการเข้ารหัส
ความเสี่ยง: การแทรกข้อมูลที่เป็นอันตรายแบบไร้สาย
ช่องทางการสื่อสารแบบไร้สายจากเครื่องสู่เครื่อง (บลูทูธคลาสสิก, BLE, NFC, Wifi P2P) อาจถูกดัดแปลงโดยใช้ข้อมูลที่เป็นอันตราย ผู้โจมตีที่มีทักษะเพียงพอจะระบุโปรโตคอลการสื่อสารที่ใช้และดัดแปลง ขั้นตอนการแลกเปลี่ยนข้อมูลได้ เช่น โดยการแอบอ้างเป็นปลายทางใดปลายทางหนึ่ง ส่ง เพย์โหลดที่สร้างขึ้นมาโดยเฉพาะ การเข้าชมที่เป็นอันตรายประเภทนี้อาจลดประสิทธิภาพการทำงานของแอปพลิเคชัน และในกรณีที่เลวร้ายที่สุดอาจทำให้เกิดลักษณะการทำงานที่ไม่คาดคิดของแอปพลิเคชันและอุปกรณ์ หรือทำให้เกิดการโจมตี เช่น DoS, การแทรกคำสั่ง หรือการยึดครองอุปกรณ์
การลดปัญหา
Android มี API ที่มีประสิทธิภาพสำหรับนักพัฒนาแอปเพื่อจัดการการสื่อสารระหว่างเครื่อง เช่น บลูทูธคลาสสิก, BLE, NFC และ Wi-Fi P2P โดยควรใช้ร่วมกับตรรกะการตรวจสอบข้อมูลที่ใช้ด้วยความระมัดระวัง เพื่อล้างข้อมูลที่แลกเปลี่ยนระหว่างอุปกรณ์ 2 เครื่อง
โซลูชันนี้ควรนำไปใช้ในระดับแอปพลิเคชันและควรมีการตรวจสอบเพื่อยืนยันว่าข้อมูลมีความยาวและรูปแบบตามที่คาดไว้ รวมถึงมีเพย์โหลดที่ถูกต้องซึ่งแอปพลิเคชันสามารถตีความได้
ข้อมูลโค้ดต่อไปนี้แสดงตรรกะการตรวจสอบข้อมูลตัวอย่าง การดำเนินการนี้ใช้ตัวอย่างของนักพัฒนาแอป 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 ระหว่างอุปกรณ์ 2 เครื่องอาจตกเป็นเป้าหมายของผู้ใช้ที่เป็นอันตราย ซึ่งสนใจดักฟังการสื่อสาร ในกรณีนี้ ลิงก์ทางกายภาพ ที่จำเป็นถือเป็นเลเยอร์ความปลอดภัยเพิ่มเติม เนื่องจากผู้โจมตีต้องได้รับ สิทธิ์เข้าถึงสายเคเบิลที่เชื่อมต่อเทอร์มินัลจึงจะสามารถดักฟังข้อความ ได้ เวกเตอร์การโจมตีอีกอย่างคืออุปกรณ์ 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 เป็น 0 จะทําให้อุปกรณ์ค้นพบได้ตราบใดที่แอปพลิเคชันทํางานอยู่เบื้องหลังหรือเบื้องหน้า สำหรับข้อกำหนดบลูทูธแบบคลาสสิก อุปกรณ์ที่ค้นพบได้จะออกอากาศข้อความการค้นหาที่เฉพาะเจาะจงอย่างต่อเนื่อง ซึ่งช่วยให้อุปกรณ์อื่นๆ ดึงข้อมูลอุปกรณ์หรือเชื่อมต่อกับอุปกรณ์ได้ ใน
สถานการณ์ดังกล่าว บุคคลที่สามที่เป็นอันตรายอาจดักรับข้อความดังกล่าวและเชื่อมต่อ
กับอุปกรณ์ที่ใช้ Android ได้ เมื่อเชื่อมต่อแล้ว ผู้โจมตีจะทำการโจมตีเพิ่มเติมได้ เช่น การขโมยข้อมูล, DoS หรือการแทรกคำสั่ง
การลดปัญหา
ไม่ควรกำหนดให้ EXTRA_DISCOVERABLE_DURATION เป็น 0 หากไม่ได้ตั้งค่าพารามิเตอร์
EXTRA_DISCOVERABLE_DURATION โดยค่าเริ่มต้น Android จะทำให้อุปกรณ์
ค้นพบได้เป็นเวลา 2 นาที ค่าสูงสุดที่ตั้งค่าได้สำหรับพารามิเตอร์
EXTRA_DISCOVERABLE_DURATION คือ 2 ชั่วโมง (7200 วินาที) เราขอแนะนำให้กำหนดระยะเวลาที่ค้นพบได้ให้สั้นที่สุดเท่าที่จะเป็นไปได้ตาม Use Case ของแอปพลิเคชัน
ความเสี่ยง: NFC - ตัวกรอง Intent ที่โคลน
แอปพลิเคชันที่เป็นอันตรายสามารถลงทะเบียนตัวกรอง Intent เพื่ออ่านแท็ก NFC ที่เฉพาะเจาะจงหรืออุปกรณ์ที่เปิดใช้ NFC ได้ ตัวกรองเหล่านี้สามารถจำลองตัวกรองที่กำหนดโดยแอปพลิเคชันที่ถูกต้อง ทำให้ผู้โจมตีอ่านเนื้อหาของข้อมูล NFC ที่แลกเปลี่ยนได้ โปรดทราบว่าเมื่อกิจกรรม 2 รายการระบุ ตัวกรอง Intent เดียวกันสำหรับแท็ก NFC ที่เฉพาะเจาะจง ระบบจะแสดงตัวเลือกกิจกรรม ดังนั้นผู้ใช้จะต้องเลือกแอปพลิเคชันที่เป็นอันตรายเพื่อให้การโจมตีสำเร็จ อย่างไรก็ตาม การรวมตัวกรอง Intent กับการปกปิดยังคงทำให้สถานการณ์นี้เกิดขึ้นได้ การโจมตีนี้มีความสำคัญเฉพาะในกรณีที่ข้อมูลที่แลกเปลี่ยนผ่าน NFC ถือเป็นข้อมูลที่มีความละเอียดอ่อนสูง
การลดปัญหา
เมื่อใช้ความสามารถในการอ่าน NFC ภายในแอปพลิเคชัน คุณจะใช้ตัวกรอง Intent ร่วมกับบันทึกแอปพลิเคชัน Android (AAR) ได้ การฝังระเบียน AAR ไว้ในข้อความ NDEF จะช่วยให้มั่นใจได้ว่ามีเพียงแอปพลิเคชันที่ถูกต้องและกิจกรรมการจัดการ NDEF ที่เกี่ยวข้องเท่านั้นที่จะเริ่มต้น ซึ่งจะป้องกันไม่ให้แอปพลิเคชันหรือกิจกรรมที่ไม่ต้องการอ่านแท็กหรือข้อมูลอุปกรณ์ที่มีความละเอียดอ่อนสูง ซึ่งแลกเปลี่ยนผ่าน NFC
ความเสี่ยง: NFC - การตรวจสอบข้อความ NDEF ไม่สำเร็จ
เมื่ออุปกรณ์ที่ใช้ระบบ Android ได้รับข้อมูลจากแท็ก NFC หรืออุปกรณ์ที่เปิดใช้ NFC ระบบจะเรียกใช้แอปพลิเคชันหรือกิจกรรมที่เฉพาะเจาะจง ซึ่งกำหนดค่าให้จัดการข้อความ NDEF ที่อยู่ในแท็กหรืออุปกรณ์นั้นโดยอัตโนมัติ ตามตรรกะที่ใช้ในแอปพลิเคชัน ข้อมูลที่อยู่ในแท็ก หรือได้รับจากอุปกรณ์จะแสดงต่อกิจกรรมอื่นๆ เพื่อทริกเกอร์ การดำเนินการเพิ่มเติม เช่น การเปิดหน้าเว็บ
แอปพลิเคชันที่ไม่มีการตรวจสอบเนื้อหาข้อความ NDEF อาจอนุญาตให้ผู้โจมตี ใช้อุปกรณ์ที่เปิดใช้ NFC หรือแท็ก NFC เพื่อแทรกเพย์โหลดที่เป็นอันตรายภายใน แอปพลิเคชัน ซึ่งทำให้เกิดลักษณะการทำงานที่ไม่คาดคิดซึ่งอาจส่งผลให้มีการดาวน์โหลดไฟล์ที่เป็นอันตราย การแทรกคำสั่ง หรือ DoS
การลดปัญหา
ก่อนส่งข้อความ NDEF ที่ได้รับไปยังคอมโพเนนต์ของแอปพลิเคชันอื่นๆ ควรตรวจสอบข้อมูลภายในเพื่อให้แน่ใจว่าอยู่ในรูปแบบที่คาดไว้และมีข้อมูลที่คาดไว้ ซึ่งจะช่วยป้องกันไม่ให้มีการส่งข้อมูลที่เป็นอันตรายไปยังคอมโพเนนต์ของแอปพลิเคชันอื่นๆ โดยไม่มีการกรอง ซึ่งจะช่วยลดความเสี่ยงของลักษณะการทำงานที่ไม่คาดคิดหรือการโจมตีโดยใช้ข้อมูล NFC ที่มีการดัดแปลง
ข้อมูลโค้ดต่อไปนี้แสดงตรรกะการตรวจสอบข้อมูลตัวอย่างที่ใช้เป็นเมธอดที่มีข้อความ 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 Basis
- บันทึกแอปพลิเคชัน Android
- ข้อกำหนดของบลูทูธคลาสสิก