强制使Android WebView离线

5
我们希望使用原始的Android WebView作为一个沙盒来执行本地HTML/JS应用程序。一个主要的安全需求是将WebView完全设置为离线,并且只允许调用一些特定的javascript接口。这些接口是使用WebView.addJavascriptInterface()方法传递到javascript运行时中的。
Android应用程序本身具有访问网络的权限(android.permission.INTERNET)。
我能够禁用普通的http/https请求,但是在阻止WebSocket请求方面彻底失败了。似乎这些请求与普通的http请求不同。
一种替代方法是覆盖WebSocket JavaScript方法。但这让我感觉很糟糕,因为它违反了沙盒概念。也似乎可以使用delete来恢复原始函数指针。
另一种选择是将自定义的WebView(例如Crosswalk-Project)与我们的应用程序捆绑在一起,但是我们想避免这样做,因为编译和更新需要相当大的工作量。
我尝试了以下公共WebView接口,但似乎没有一个能够阻止WebSocket调用:
  • webSettings.setBlockNetworkLoads(true);
  • webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
  • webView.setNetworkAvailable(false);
  • WebViewClient.shouldOverrideUrlLoading() (回调函数)
  • WebViewClient.shouldInterceptRequest() (回调函数,尝试了两个版本)
  • WebChromeClient.onPermissionRequest()

在Android 4.4.4(19)和Android 5 / 5.1(21/22)上测试通过。

我正在执行的javascript:

ws = new WebSocket("wss://echo.websocket.org");

ws.onmessage = function(event) {
  console.log("received: " + event.data);
};

ws.onclose = function() {
  console.log("External Socket closed");
};

ws.onopen = function() {
  console.log("Connected to external ws");
  ws.send("Hello from " + navigator.userAgent);
};

有任何想法如何完成这个任务吗?

非常感谢。


也许不是您所期望的“请求”,但您可以设置WebViewClient的实现 http://developer.android.com/reference/android/webkit/WebViewClient.html 并强制其处于离线状态。 - eduyayo
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Oliver M.
对于WebSockets...只有一个请求。您应该将流导入管道。 - eduyayo
你说得对,对于WebSockets来说只有一个请求。但是当在JavaScript中打开WebSocket时,WebViewClient.shouldInterceptRequest()方法(或其他on*()方法)甚至不会被调用一次。只有像图片或重定向之类的东西才会被调用。这正是我的问题所在。 - Oliver M.
2个回答

1
另一种可能的方法是在加载到Web视图中的页面源代码中包含内容安全策略:
<meta http-equiv="Content-Security-Policy" content="connect-src 'none';"/>

有了这个设置,我发现为echo.websocket.org构建websocket实例只会抛出错误并在控制台记录CSP违规。

或者你可以在ShouldInterceptRequest方法中设置此标头;以下是一个简化的示例:

public override WebResourceResponse ShouldInterceptRequest(WebView view, IWebResourceRequest request)
{
    using (var stream = view.Context.Assets.Open("test.html"))
    {
        var resp = new WebResourceResponse("text/html", "UTF-8", stream);
        resp.ResponseHeaders = new Dictionary<string, string>();
        resp.ResponseHeaders.Add("Content-Security-Policy", "connect-src 'none';");
        return resp;
    }
}

请注意,对file:///android_asset/...file:///android_res/...地址的请求不会触发拦截方法,但其他file:///...地址会触发。
还要注意,我只在Android 8.0上测试过这种技术。显然,Chrome自2013年以来就支持CSP,但我无法确定这对Android的实现意味着什么。

在CSP方面,为离线页面设置更全面的策略可能是值得的。假设来源是file://,可以考虑以下内容:

default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: filesystem:;

0

很遗憾,目前没有办法在WebView中拦截WebSockets调用。您只能阻止整个JavaScript执行或网络访问。您是正确的,尝试使用自己的实现覆盖WebSocket构造函数可以轻松地通过删除被覆盖的窗口属性来解决,因此从window原型链中可访问内置构造函数再次变得可见。如果您尝试使用WebView.addJavascriptInterface覆盖WebSocket,也会发生同样的情况。

我认为,HTML应用程序缓存也不会被WebView回调拦截,甚至不需要打开JavaScript即可工作。

我想,使用没有网络访问权限的专用应用程序,并使用Binder IPC与其通信,是创建系统WebView网络沙箱的唯一机会。

捆绑定制的WebView将会带来更多困难,因为您需要找出并修补所有漏洞,然后自行更新安全修复程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接