WebView - การโหลด URI ที่ไม่ปลอดภัย
จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน
บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ
หมวดหมู่ OWASP: MASVS-CODE: คุณภาพของโค้ด
ภาพรวม
การโหลด URI ที่ไม่ปลอดภัยเกิดขึ้นเมื่อแอปพลิเคชัน Android ประเมินความถูกต้องของ URI ไม่ถูกต้องก่อนที่จะโหลดลงใน WebView
สาเหตุพื้นฐานที่อยู่เบื้องหลังช่องโหว่ประเภทนี้คือ URI ประกอบด้วยหลายส่วน ซึ่งอย่างน้อยที่สุดจะต้องมีการยืนยัน (เช่น การเพิ่มในรายการที่อนุญาต) รูปแบบและโฮสต์ (ของส่วนการให้สิทธิ์) ก่อนที่จะโหลด URI ไปยัง WebView หรือใช้ภายในแอปพลิเคชัน
ข้อผิดพลาดที่พบบ่อยที่สุดมีดังนี้
- ตรวจสอบโฮสต์แต่ไม่ตรวจสอบรูปแบบ ซึ่งจะทำให้ผู้โจมตีใช้รูปแบบต่างๆ เช่น
http://
, content://
หรือ javascript://
กับโฮสต์ที่ได้รับการตรวจสอบสิทธิ์ได้
- การแยกวิเคราะห์ URI ไม่ถูกต้อง โดยเฉพาะในกรณีที่ได้รับ URI เป็นสตริง
- ตรวจสอบรูปแบบ แต่ไม่ใช่โฮสต์ (การตรวจสอบโฮสต์ไม่เพียงพอ)
ในกรณีสุดท้ายนี้ มักเกิดขึ้นเมื่อแอปพลิเคชันต้องอนุญาตโดเมนย่อยที่กำหนดเองของโดเมนหลัก ดังนั้น แม้ว่าจะมีการดึงชื่อโฮสต์อย่างถูกต้อง แต่แอปจะใช้วิธีการต่างๆ เช่น startsWith
, endsWith,
หรือ contains
ของคลาส java.lang.String
เพื่อตรวจสอบว่ามีโดเมนหลักอยู่ในส่วนสตริงที่ดึงออกมาหรือไม่ หากใช้วิธีการเหล่านี้อย่างไม่ถูกต้อง อาจทำให้ได้ผลลัพธ์ที่ไม่ถูกต้องและบังคับให้แอปพลิเคชันเชื่อถือโฮสต์ที่อาจเป็นอันตรายอย่างไม่เหมาะสม
ผลกระทบ
ผลกระทบอาจแตกต่างกันไปตามบริบทที่ใช้โฮสต์ ในกรณีที่การโหลด URI ที่เป็นอันตราย (เช่น URI ที่หลีกเลี่ยงการกรอง/รายการที่อนุญาต) ใน WebView อาจทำให้เกิดการลักลอบใช้บัญชี (เช่น การใช้ฟิชชิง) การเรียกใช้โค้ด (เช่น การโหลด JavaScript ที่เป็นอันตราย) หรือการบุกรุกอุปกรณ์ (โค้ดที่ใช้ช่องโหว่ซึ่งส่งผ่านไฮเปอร์ลิงก์)
การลดปัญหา
เมื่อจัดการ URI สตริง สิ่งสำคัญคือต้องแยกวิเคราะห์สตริงเป็น URI และ
ตรวจสอบทั้งรูปแบบและโฮสต์
Kotlin
fun isUriTrusted(incomingUri: String, trustedHostName: String): Boolean {
try {
val uri = Uri.parse(incomingUri)
return uri.scheme == "https" && uri.host == trustedHostName
} catch (e: NullPointerException) {
throw NullPointerException("incomingUri is null or not well-formed")
}
}
Java
public static boolean isUriTrusted(String incomingUri, String trustedHostName)
throws NullPointerException {
try {
Uri uri = Uri.parse(incomingUri);
return uri.getScheme().equals("https") &&
uri.getHost().equals(trustedHostName);
} catch (NullPointerException e) {
throw new NullPointerException(
"incomingUri is null or not well-formed");
}
}
สำหรับการตรวจสอบโฮสต์ หลังจากแยกส่วน URI ที่เกี่ยวข้องแล้ว คุณควร
ตรวจสอบทั้งส่วน (ไม่ใช่บางส่วน) เพื่อระบุได้อย่างถูกต้องว่า
โฮสต์เชื่อถือได้หรือไม่ เมื่อหลีกเลี่ยงการใช้วิธีการต่างๆ เช่น startsWith
หรือ endsWith
ไม่ได้
คุณควรใช้ไวยากรณ์ที่ถูกต้องและไม่มองข้ามอักขระหรือสัญลักษณ์ที่จำเป็น (เช่น endsWith
ต้องมีอักขระจุด ".
" ก่อนชื่อโดเมนเพื่อให้ตรงกันอย่างถูกต้อง) การละเลยอักขระเหล่านี้อาจทำให้การจับคู่ไม่ถูกต้องและกระทบต่อความปลอดภัย เนื่องจากโดเมนย่อยซ้อนกันได้ไม่จำกัด การจับคู่นิพจน์ทั่วไปจึงไม่ใช่กลยุทธ์ที่แนะนำสำหรับการตรวจสอบชื่อโฮสต์
ผู้ร่วมให้ข้อมูล: Dimitrios Valsamaras และ Michael Peck จาก Microsoft Threat
Intelligence
แหล่งข้อมูล
ตัวอย่างเนื้อหาและโค้ดในหน้าเว็บนี้ขึ้นอยู่กับใบอนุญาตที่อธิบายไว้ในใบอนุญาตการใช้เนื้อหา Java และ OpenJDK เป็นเครื่องหมายการค้าหรือเครื่องหมายการค้าจดทะเบียนของ Oracle และ/หรือบริษัทในเครือ
อัปเดตล่าสุด 2025-07-27 UTC
[[["เข้าใจง่าย","easyToUnderstand","thumb-up"],["แก้ปัญหาของฉันได้","solvedMyProblem","thumb-up"],["อื่นๆ","otherUp","thumb-up"]],[["ไม่มีข้อมูลที่ฉันต้องการ","missingTheInformationINeed","thumb-down"],["ซับซ้อนเกินไป/มีหลายขั้นตอนมากเกินไป","tooComplicatedTooManySteps","thumb-down"],["ล้าสมัย","outOfDate","thumb-down"],["ปัญหาเกี่ยวกับการแปล","translationIssue","thumb-down"],["ตัวอย่าง/ปัญหาเกี่ยวกับโค้ด","samplesCodeIssue","thumb-down"],["อื่นๆ","otherDown","thumb-down"]],["อัปเดตล่าสุด 2025-07-27 UTC"],[],[],null,["# Webviews – Unsafe URI Loading\n\n\u003cbr /\u003e\n\n**OWASP category:** [MASVS-CODE: Code Quality](https://mas.owasp.org/MASVS/10-MASVS-CODE)\n\n\nOverview\n--------\n\nAn unsafe URI Loading occurs when an Android application fails to correctly\nevaluate the validity of a URI before loading it into a WebView.\n\nThe underlying reason behind this type of vulnerability is that a\n[URI consists of multiple parts](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/net/URI.html), of which, at a minimum, the scheme and the\nhost (of the authority part) must be verified (e.g. allowlisted) before the URI\ngets loaded to a WebView or is used internally by the application.\n\nThe most common mistakes include:\n\n- Checking the host but not the scheme, allowing an attacker to use schemes like `http://`, `content://` or `javascript://` with an authenticated host.\n- Failing to parse the URI correctly, especially in cases where the URI is received as a string.\n- Validating the scheme but not the host (insufficient host validation).\n\nRegarding the last case, this usually occurs when the application needs to allow\narbitrary subdomains of a primary domain. So, even if the hostname has been\nextracted correctly, the app uses methods such as `startsWith`, `endsWith,` or\n`contains` of the `java.lang.String` class to validate the presence of a primary\ndomain in the extracted string section. Used incorrectly, these methods may lead\nto faulty results and force the application to improperly trust a potentially\nmalicious host.\n\nImpact\n------\n\nDepending on the context in which the host is used, the impact can vary. In\ncases where loading a malicious URI (i.e., one that bypassed\nfiltering/allowlist) in a WebView could potentially lead to account takeover\n(e.g. using phishing), code execution (e.g., loading malicious JavaScript), or\ndevice compromise (exploit code delivered using hyperlink).\n\nMitigations\n-----------\n\nWhen handling string URIs, it is important to parse the string as a URI and\nvalidate both the scheme and the host: \n\n### Kotlin\n\n fun isUriTrusted(incomingUri: String, trustedHostName: String): Boolean {\n try {\n val uri = Uri.parse(incomingUri)\n return uri.scheme == \"https\" && uri.host == trustedHostName\n } catch (e: NullPointerException) {\n throw NullPointerException(\"incomingUri is null or not well-formed\")\n }\n }\n\n### Java\n\n public static boolean isUriTrusted(String incomingUri, String trustedHostName)\n throws NullPointerException {\n try {\n Uri uri = Uri.parse(incomingUri);\n return uri.getScheme().equals(\"https\") &&\n uri.getHost().equals(trustedHostName);\n } catch (NullPointerException e) {\n throw new NullPointerException(\n \"incomingUri is null or not well-formed\");\n }\n }\n\nFor host validation, after isolating the corresponding URI part, it is important\nto validate it entirely (rather than partially) to accurately identify whether\nthe host is trusted or not. When using methods like `startsWith` or `endsWith`\ncan't be avoided, it is important to use the correct syntax and not overlook\nnecessary characters or symbols (for example, `endsWith` requires the \"`.`\" dot\ncharacter before the domain name for an accurate match). Neglecting these\ncharacters may lead to inaccurate matches and compromise security. Since\nsubdomains can be infinitely nested, regular expression matching is not a\nrecommended strategy for validating hostnames.\n\nContributors: Dimitrios Valsamaras and Michael Peck of Microsoft Threat\nIntelligence\n\nResources\n---------\n\n- [getHost() documentation](/reference/java/net/URI#getHost())\n- [getScheme() documentation](/reference/java/net/URI#getScheme())"]]