Skip to content

MASTG-TEST-0077: Teste de Manipuladores de Protocolo do WebView

Visão Geral

Análise Estática

  • Testando como WebViews carregam conteúdo
  • Testando acesso a arquivos em WebView
  • Verificando detecção de números de telefone

Testando como WebViews carregam conteúdo

Se uma WebView estiver carregando conteúdo do diretório de dados do aplicativo, os usuários não devem conseguir alterar o nome do arquivo ou o caminho a partir do qual o arquivo é carregado, e não devem conseguir editar o arquivo carregado.

Isso apresenta um problema especialmente em UIWebViews que carregam conteúdo não confiável através dos métodos obsoletos loadHTMLString:baseURL: ou loadData:MIMEType:textEncodingName:baseURL: e definindo o parâmetro baseURL como nil ou para esquemas de URL file: ou applewebdata:. Nesse caso, para evitar acesso não autorizado a arquivos locais, a melhor opção é defini-lo como about:blank. No entanto, a recomendação é evitar o uso de UIWebViews e mudar para WKWebViews.

Aqui está um exemplo de uma UIWebView vulnerável de "Where's My Browser?":

let scenario2HtmlPath = Bundle.main.url(forResource: "web/UIWebView/scenario2.html", withExtension: nil)
do {
    let scenario2Html = try String(contentsOf: scenario2HtmlPath!, encoding: .utf8)
    uiWebView.loadHTMLString(scenario2Html, baseURL: nil)
} catch {}

A página carrega recursos da internet usando HTTP, permitindo que um potencial MITM exfiltrar segredos contidos em arquivos locais, por exemplo, em preferências compartilhadas.

Ao trabalhar com WKWebViews, a Apple recomenda usar loadHTMLString:baseURL: ou loadData:MIMEType:textEncodingName:baseURL: para carregar arquivos HTML locais e loadRequest: para conteúdo web. Normalmente, os arquivos locais são carregados em combinação com métodos incluindo, entre outros: pathForResource:ofType:, URLForResource:withExtension: ou init(contentsOf:encoding:).

Pesquise no código-fonte os métodos mencionados e inspecione seus parâmetros.

Exemplo em Objective-C:

- (void)viewDidLoad
{
    [super viewDidLoad];
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];

    self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(10, 20,
        CGRectGetWidth([UIScreen mainScreen].bounds) - 20,
        CGRectGetHeight([UIScreen mainScreen].bounds) - 84) configuration:configuration];
    self.webView.navigationDelegate = self;
    [self.view addSubview:self.webView];

    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"example_file" ofType:@"html"];
    NSString *html = [NSString stringWithContentsOfFile:filePath
                                encoding:NSUTF8StringEncoding error:nil];
    [self.webView loadHTMLString:html baseURL:[NSBundle mainBundle].resourceURL];
}

Exemplo em Swift de "Where's My Browser?":

let scenario2HtmlPath = Bundle.main.url(forResource: "web/WKWebView/scenario2.html", withExtension: nil)
do {
    let scenario2Html = try String(contentsOf: scenario2HtmlPath!, encoding: .utf8)
    wkWebView.loadHTMLString(scenario2Html, baseURL: nil)
} catch {}

Se você tiver apenas o binário compilado, também pode pesquisar por esses métodos usando rabin2:

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

Em um caso como este, é recomendado realizar análise dinâmica para garantir que isso esteja de fato sendo usado e de qual tipo de WebView. O parâmetro baseURL aqui não apresenta um problema, pois será definido como "null", mas pode ser um problema se não for definido corretamente ao usar uma UIWebView. Veja "Verificando como WebViews são carregadas" para um exemplo sobre isso.

Além disso, você também deve verificar se o aplicativo está usando o método loadFileURL: allowingReadAccessToURL:. Seu primeiro parâmetro é URL e contém a URL a ser carregada na WebView, seu segundo parâmetro allowingReadAccessToURL pode conter um único arquivo ou um diretório. Se contiver um único arquivo, esse arquivo estará disponível para a WebView. No entanto, se contiver um diretório, todos os arquivos nesse diretório serão disponibilizados para a WebView. Portanto, vale a pena inspecionar isso e, caso seja um diretório, verificar se não há dados sensíveis dentro dele.

Exemplo em Swift de "Where's My Browser?":

var scenario1Url = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
scenario1Url = scenario1Url.appendingPathComponent("WKWebView/scenario1.html")
wkWebView.loadFileURL(scenario1Url, allowingReadAccessTo: scenario1Url)

Neste caso, o parâmetro allowingReadAccessToURL contém um único arquivo "WKWebView/scenario1.html", o que significa que a WebView tem acesso exclusivo a esse arquivo.

No binário compilado, você pode usar rabin2:

$ rabin2 -zz ./WheresMyBrowser | grep -i "loadFileURL"
237 0x0002dff1 37 (4.__TEXT.__objc_methname) ascii loadFileURL:allowingReadAccessToURL:

Testando acesso a arquivos em WebView

Se você encontrou uma UIWebView sendo usada, então o seguinte se aplica:

  • O esquema file:// está sempre habilitado.
  • O acesso a arquivos a partir de URLs file:// está sempre habilitado.
  • O acesso universal a partir de URLs file:// está sempre habilitado.

Em relação às WKWebViews:

  • O esquema file:// também está sempre habilitado e não pode ser desativado.
  • Ele desativa o acesso a arquivos a partir de URLs file:// por padrão, mas pode ser habilitado.

As seguintes propriedades da WebView podem ser usadas para configurar o acesso a arquivos:

  • allowFileAccessFromFileURLs (WKPreferences, false por padrão): permite que JavaScript executando no contexto de uma URL de esquema file:// acesse conteúdo de outras URLs de esquema file://.
  • allowUniversalAccessFromFileURLs (WKWebViewConfiguration, false por padrão): permite que JavaScript executando no contexto de uma URL de esquema file:// acesse conteúdo de qualquer origem.

Por exemplo, é possível definir a propriedade não documentada allowFileAccessFromFileURLs fazendo isso:

Objective-C:

[webView.configuration.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];

Swift:

webView.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")

Se uma ou mais das propriedades acima estiverem ativadas, você deve determinar se elas são realmente necessárias para o aplicativo funcionar corretamente.

Verificando detecção de números de telefone

No Safari no iOS, a detecção de números de telefone está ativada por padrão. No entanto, você pode querer desativá-la se sua página HTML contiver números que podem ser interpretados como números de telefone, mas não são, ou para evitar que o documento DOM seja modificado quando analisado pelo navegador. Para desativar a detecção de números de telefone no Safari no iOS, use a meta tag format-detection (<meta name = "format-detection" content = "telephone=no">). Um exemplo disso pode ser encontrado na documentação do desenvolvedor Apple. Links de telefone devem então ser usados (por exemplo, <a href="tel:1-408-555-5555">1-408-555-5555</a>) para criar explicitamente um link.

Análise Dinâmica

Se for possível carregar arquivos locais através de uma WebView, o aplicativo pode ser vulnerável a ataques de travessia de diretório. Isso permitiria acesso a todos os arquivos dentro do sandbox ou até mesmo escapar do sandbox com acesso completo ao sistema de arquivos (se o dispositivo estiver com jailbreak). Portanto, deve ser verificado se um usuário pode alterar o nome do arquivo ou o caminho a partir do qual o arquivo é carregado, e eles não devem conseguir editar o arquivo carregado.

Para simular um ataque, você pode injetar seu próprio JavaScript na WebView com um proxy de interceptação ou simplesmente usando instrumentação dinâmica. Tente acessar o armazenamento local e quaisquer métodos e propriedades nativas que possam estar expostos ao contexto JavaScript.

Em um cenário real, o JavaScript só pode ser injetado através de uma vulnerabilidade permanente de Cross-Site Scripting no backend ou um ataque MITM. Consulte a OWASP XSS Prevention Cheat Sheet e o capítulo "Comunicação de Rede iOS)" para mais informações.

No que diz respeito a esta seção, aprenderemos sobre:

  • Testando como WebViews carregam conteúdo
  • Determinando acesso a arquivos em WebView

Testando como WebViews carregam conteúdo

Se o "cenário 2" da WKWebView do aplicativo "Where's My Browser?" for carregado, o aplicativo fará isso chamando URLForResource:withExtension: e loadHTMLString:baseURL.

Para inspecionar isso rapidamente, você pode usar frida-trace e rastrear todos os métodos loadHTMLString e URLForResource:withExtension:.

$ frida-trace -U "Where's My Browser?"
    -m "*[WKWebView *loadHTMLString*]" -m "*[* URLForResource:withExtension:]"

 14131 ms  -[NSBundle URLForResource:0x1c0255390 withExtension:0x0]
 14131 ms  URLForResource: web/WKWebView/scenario2.html
 14131 ms  withExtension: 0x0
 14190 ms  -[WKWebView loadHTMLString:0x1c0255390 baseURL:0x0]
 14190 ms   HTMLString: <!DOCTYPE html>
    <html>
        ...
        </html>

 14190 ms  baseURL: nil

Neste caso, baseURL está definido como nil, o que significa que a origem efetiva é "null". Você pode obter a origem efetiva executando window.origin a partir do JavaScript da página (este aplicativo tem um helper de exploração que permite escrever e executar JavaScript, mas você também pode implementar um MITM ou simplesmente usar Frida para injetar JavaScript, por exemplo, via evaluateJavaScript:completionHandler de WKWebView).

Como uma observação adicional em relação às UIWebViews, se você recuperar a origem efetiva de uma UIWebView onde baseURL também está definido como nil, 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 Same-Origin Policy e permite acesso a arquivos locais e quaisquer recursos web. Neste caso, seria melhor definir baseURL como "about:blank", desta forma, a Same-Origin Policy impediria o acesso entre origens. No entanto, a recomendação aqui é evitar completamente o uso de UIWebViews e optar por WKWebViews.

Determinando acesso a arquivos em WebView

Mesmo não tendo o código-fonte original, você pode determinar rapidamente se as WebViews do aplicativo permitem acesso a arquivos e qual tipo. Para isso, simplesmente navegue até a WebView de destino no aplicativo e inspecione todas as suas instâncias, para cada uma delas obtenha os valores mencionados na análise estática, ou seja, allowFileAccessFromFileURLs e allowUniversalAccessFromFileURLs. Isso só se aplica a WKWebViews (UIWebViews sempre permitem acesso a arquivos).

Continuamos com nosso exemplo usando o aplicativo "Where's My Browser?" e Frida REPL, estenda o script com o seguinte conteúdo:

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

Se você executá-lo agora, terá todas as informações de que precisa:

$ 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

Tanto allowFileAccessFromFileURLs quanto allowUniversalAccessFromFileURLs estão definidos como "0", o que significa que estão desativados. Neste aplicativo, podemos ir para a configuração da WebView e habilitar allowFileAccessFromFileURLs. Se fizermos isso e reexecutarmos o script, veremos como ele está definido como "1" desta vez:

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

allowFileAccessFromFileURLs:  1