Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

We want to use the stock Android WebView as a sandbox to execute local HTML/JS applications. A main security requirement is to set the WebView completely offline and only allow certain javascript interfaces to be called. These interfaces are passed into the javascript runtime using the WebView.addJavascriptInterface() method.

The Android application itself has the permission to access the network (android.permission.INTERNET).

I'm able to disable normal http/https requests, but totally failed in blocking WebSocket requests . It seems these are handled different to normal http requests.

One alternative is to overwrite the WebSocket JavaScript method. But this gives me a bad feeling as it is against the sandbox concept. It also seems to be possible to use delete to restore the original function pointer.

Another alternative would be to bundle an own customized WebView (e.g. Crosswalk-Project) with our application, but would like to avoid it as the compilation and updates are quite an effort.

I tried the following public WebView interfaces but none of them seems to block WebSocket calls:

  • webSettings.setBlockNetworkLoads(true);
  • webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
  • webView.setNetworkAvailable(false);
  • WebViewClient.shouldOverrideUrlLoading() (callback)
  • WebViewClient.shouldInterceptRequest() (callback, tried both versions)
  • WebChromeClient.onPermissionRequest()
  • Tested on Android 4.4.4 (19) and Android 5/5.1 (21/22).

    The javascript I'm executing:

    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);
    

    Any ideas how this could be done?

    Thanks a lot

    Maybe not your desired "request" but you can set your implementation of WebViewClient developer.android.com/reference/android/webkit/… and force it to be offline – eduyayo May 19, 2015 at 12:40 @eduyayo Thanks, but already tried this (see question). The callbacks of the WebViewClient are only called for "normal" http requests. For WebSockets the callbacks are not working. – Oliver M. May 19, 2015 at 12:57 You are right, for WebSockets it's only one request. But the WebViewClient.shouldInterceptRequest() method (or other on*() methods) are not called even once when opening a WebSocket in JavaScript. Only for things like images or redirects they are called. That's exactly my issue. – Oliver M. May 19, 2015 at 13:28

    Another possible approach is to include a content security policy in the source of the page loaded into the webview:

    <meta http-equiv="Content-Security-Policy" content="connect-src 'none';"/>
    

    with this in place, I found that constructing the websocket instance for echo.websocket.org simply throws an error and logs a CSP violation to the console.

    Alternatively you could set this header in your ShouldInterceptRequest method; here's a simplified example:

    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;
    

    Note that requests to file:///android_asset/... and file:///android_res/... addresses don't trigger the intercept method, but other file:///... addresses do.

    Also be aware that I've only tested this technique on Android 8.0. Apparently Chrome has supported CSP since 2013, but I can't say for sure what this means for Android's implementation.

    On the topic of CSPs, it may be worth setting a more comprehensive policy for offline pages. Assuming the origin is file://, perhaps something like this:

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

    Unfortunately, there is currently no way of intercepting WebSockets calls in WebView. You can only block out entire JavaScript execution or network access. You are correct that trying to override WebSocket constructor with your own implementation can be worked around easily by deleting the overridden window property, so the built-in constructor accessible from the window prototype chain gets visible again. The same will happen if you try to override WebSocket with WebView.addJavascriptInterface.

    I think, HTML Application Cache is also not intercepted by WebView callbacks, and it doesn't even require Javascript to be turned on in order to work.

    I guess, your only chance for creating a WebView network sandbox with the system WebView is to use a dedicated app with no network access, and communicate to it using Binder IPC.

    Bundling customized WebView will create even more difficulties, as you will need to find out and plug all the holes, and then keep updating it with security fixes on your own.

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.