Secure implementation of WebView in iOS applications

This post discusses how to ensure secure WebViews and how to keep the attack surface restricted.

Wojciech Reguła 2021.10.07   –   9 MIN read

TL;DR

  • Do not use UIWebView.
  • Make sure your Info.plist doesn’t contain App Transport Security Exceptions.
  • Follow the least privilege principle.
  • Consider disabling JavaScript.
  • Code JavaScript-ObjC/Swift bridges carefully.
  • Follow good mobile application development practices -> see our Guidelines on mobile application security – iOS edition.

Context

Recently I had a chance to observe a lot of new WebView applications, so I decided to create this article. A few years ago, if someone wanted to create a multiplatform application it was almost necessary to create a different codebase on each platform. Then, the cross-platform frameworks entered the market that made universal coding easier. One way of universal coding is to use WebView. The idea was simple – create an application in web technologies (HTML, CSS, JS) and render it within the native application. So,  WebView is just an embedded browser in your application. Such a technology introduced vulnerabilities characteristic to web applications to native applications. Since WebView can be treated as a browser it uses the same mechanisms that can be abused as well. As it turned out, the exploitation results can be even more harmful. What if the application wants to obtain some resources saved on your device like photos or contacts? Well, developers need to create JavaScript<->Objective-C/Swift bridges that can be exploited using simple Cross-Site Scripting vulnerability. In the next subsection, I will show you how to create secure WebView applications including the most common threats.

Deprecated UIWebView – major security flaw

This subsection could be shortened to “do not use UIWebViews”. The UIWebView is the old Apple’s API present in iOS since version 2.0, so since 2008. If you follow the history of the vulnerabilities you probably know that in 2008 most of the modern browser security features were not yet invented. Do not expect the API created released in 2008 to implement those security mechanisms either. The UIWebView was deprecated in iOS 12.0 and should no longer be used in your code. If your application still uses the UIWebView, the best recommendation I can give you is to rewrite it to WKWebView. Before you start doing the refactoring, I’d suggest reading the next subsection about WKWebViews as their implementation can be coded insecurely too.

But why do I not recommend using the UIWebView? If you need concrete arguments, you can find them below:

  1. UIWebView doesn’t have a functionality that allows disabling JavaScript. So if your application doesn’t use JS and you want to follow the least privilege principle, you cannot switch it off.
  2. There is no feature handling mixed content. You cannot verify if everything was loaded using HTTPS protocol. his functionality shouldn’t be a case, because you shouldn’t add any App Transport Security exceptions (exceptions that allow insecure HTTP connections).
  3. UIWebView doesn’t implement the out-of-process rendering as WKWebView does. So, if attackers find a memory corruption vulnerability in the UIWebView they will be able to exploit it in the context of your application.
  4. File access via file:// scheme is always turned on. What’s even worse is accessing files via that scheme doesn’t follow the Same Origin Policy mechanism. It means that if the attackers exploit a Cross-Site Scripting vulnerability in your WebView they can  load files available from the application’s sandbox and then send them to their server.

As a good example of insecurity UIWebView I’ll show you a vulnerability I found in Apple’s Dictionary.app on macOS:

The Dictionary.app allows of course translation from language A to B. Apple wasn’t able to create all dictionaries, so you can create yourdictionary with for example an ethnic dialect. The translated words were then displayed in the UIWebView without any validation. I was wondering if there is a possibility to exploit the file:// handler and steal local files, so I created the following dictionary entry:

Then, I opened the Dictionary.app, launched netcat on 127.0.0.1:8000 and contents of the /etc/passwd were transferred:

I think you are now convinced that using UIWebView is strongly not recommended.

WKWebView on iOS devices

WKWebView is that API you should use to load web content in your application. The “WK” prefix comes from the WebKit, the browser engine. The WKWebView is a modern API applying all the modern web security mechanisms, it’s still maintained by Apple and gets updates. The good thing about WKWebView is that it does out-of-process rendering, so if the attackers find a memory corruption vulnerability in it, your application’s process is still isolated.

Let’s start from the Info.plist configuration. In the article about secure networking on iOS I wrote about App Transport Security exceptions. The recommendations apply also for the WKWebView. Make sure you do not allow unencrypted HTTP connections. The WKWebView can be treated as a web browser, so if the attacker can perform a Man-In-The-Middle attack, they can steal your cookies/authorization tokens, execute JavaScript in your app’s context and thus for example call JavaScript<->Objective-C/Swift bridges. You can verify if the content was loaded fully with encrypted connections with the following code:

import UIKit
import WebKit
 
class ViewController: UIViewController {
 
   @IBOutlet weak var webContentView: UIView!
   var configuration: WKWebViewConfiguration?
   var webView: WKWebView?
 
       override func loadWebView() {
           self.configuration = WKWebViewConfiguration()
           self.webView = WKWebView(frame: self.webContentView.bounds, configuration: configuration!)
           self.webContentView.addSubview(self.webView!)
          
           let url = URL(string: "https://securing.biz")!
           let request = URLRequest(url: url)
           self.webView!.load(request)
          
           print("Has only secure content?: \(self.webView!.hasOnlySecureContent)")
       }
}

Then, you need to somehow load the HTML content. There are two approaches: the first one loads HTML content from the application’s package (local) and the second is to load the HTML content from your website. Make sure you load content that you fully control. If you load JavaScript code from the external resources, you can verify it’s cryptographic hash with integrity attributes. For high-risk applications, it’s recommended to apply reverse engineering protections. In the WebView world, you can minify the JavaScript files or even obfuscate them.

Now, let’s talk about the hardening. The file:// scheme is always enabled in the WKWebView, but it cannot (by default) access files. That mechanism can be enabled, but please keep in mind the least privilege principle. If your WebView doesn’t necessarily have to access files don’t turn it on. 

The opposite thing is a JavaScript interpreter that is turned on by default. If your website doesn’t use JS, it’s recommended (again – the least privilege principle) to turn it off. You can use the following code:

let webPreferences = WKPreferences()
webPreferences.javaScriptEnabled = false
self.configuration?.preferences = webPreferences

The last feature that I wanted to discuss in this article are bridges. The WKWebView allows calling native code from JavaScript. You now probably realize how harmful it could be if not properly coded. Two years ago I was pentesting an iOS application that used such bridges to get photos from the user’s photo library. As I found a Stored Cross-Site Scripting Vulnerability that allowed me to execute JavaScript code on every instance of that application, I was able to steal all the photos from users’ photo libraries and send them to my server. The native code is called via postMessage API.

Native code:

// first register the controller
self.configuration?.userContentController.add(self, name: "SystemAPI")
[...]
// and expose native methods
extension ViewController : WKScriptMessageHandler {
   func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
       if message.name == "SystemAPI" {
           guard let dictionary = message.body as? [String: AnyObject],
                 let command = dictionary["command"] as? String,
                 let parameter = dictionary["parameter"] as? String else {
               return
           }
           switch command {
           case "loadFile":
               loadFile(path: parameter)
           case "loadContact":
               loadContact(name: parameter)
           default:
               print("Command not recognized")
           }
       }      
   }
}

JavaScript code:

<script>
window.webkit.messageHandlers.SystemAPI.postMessage({ "command":"loadFile", "parameter":"/etc/passwd"});
</script>

The code example I pasted is of course not well-designed because it allows loading any file or any contact. When coding bridges make sure your methods are as limited as possible. So even if the attackers will somehow inject code to your WebView the attack surface will be tight. So, do not expose excessive methods, strictly validate the parameters and make them limited (in this case instead of loading file from path, you can load files by ID from the specified in the function directory). In order to additionally prevent Cross-Site Scripting vulnerabilities consider also implementing the Content Security Policy mechanism. Despite it’s only an additional layer of security it can stop the attackers by blocking XSS vulnerabilities.

Summary

Using WebViews in native applications may boost the development, as the same HTML/CSS/JS code can be used across all the platforms the application supports. That technology is indeed convenient but comes with new risks. In this article, I wanted to show you 2 APIs present in Apple’s environment. The old one – UIWebView is considered  insecure and should no longer be used. WKWebView is the right API to implement WebViews. Unfortunately, using even the modern API may lead to vulnerabilities. Developers have to make sure that their code is not vulnerable to both web-related and native attacks. This article presented how to implement secure WebViews and how to limit the attack surface.

During the iOS application development process, it’s also critical to follow best practices. We’ve put up a handbook that compiles all of our iOS security knowledge into one place. You may go to it by clicking on the link below.

Feel free to reach me out. You can find me on Twitter on LinkedIn

Wojciech Reguła
Wojciech Reguła Principal IT Security Consultant
Head of Mobile Security