Skip to content

MASTG-TEST-0076: Testando WebViews no iOS

Visão Geral

Análise Estática

Para a análise estática, vamos focar principalmente nos seguintes pontos, mantendo UIWebView e WKWebView em escopo:

  • Identificação do uso de WebView
  • Teste se o JavaScript está habilitado
  • Teste para Conteúdo Misto (Mixed Content)
  • Teste para Manipulação de URI em WebView

Identificação do Uso de WebView

Procure por usos das classes de WebView mencionadas acima pesquisando no Xcode.

No binário compilado, você pode pesquisar em seus símbolos ou strings, por exemplo, usando rabin2 assim:

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 pesquisar por métodos conhecidos dessas classes de WebView. Por exemplo, pesquise 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

Você também pode desmembrá-lo (demangle):

$ xcrun swift-demangle __T0So9WKWebViewCABSC6CGRectV5frame_So0aB13ConfigurationC13configurationtcfcTO

---> @nonobjc __C.WKWebView.init(frame: __C_Synthesized.CGRect,
                                configuration: __C.WKWebViewConfiguration) -> __C.WKWebView

Teste se o JavaScript está Habilitado

Primeiro, lembre-se de que o JavaScript não pode ser desativado para UIWebViews.

Para WKWebViews, como uma prática recomendada, o JavaScript deve ser desativado, a menos que seja explicitamente necessário. Para verificar se o JavaScript foi desativado corretamente, pesquise no projeto por usos de WKPreferences e certifique-se de que a propriedade javaScriptEnabled esteja definida como false:

let webPreferences = WKPreferences()
webPreferences.javaScriptEnabled = false

Se você tiver apenas o binário compilado, pode pesquisar por isso nele usando rabin2:

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

Se scripts de usuário foram definidos, eles continuarão sendo executados, pois a propriedade javaScriptEnabled não os afetará. Consulte WKUserContentController e WKUserScript para obter mais informações sobre a injeção de scripts de usuário em WKWebViews.

Teste para Conteúdo Misto (Mixed Content)

Ao contrário dos UIWebViews, ao usar WKWebViews é possível detectar conteúdo misto (conteúdo HTTP carregado de uma página HTTPS). Usando o método hasOnlySecureContent, pode-se verificar se todos os recursos na página foram carregados por meio de conexões criptografadas com segurança. Este exemplo de [#thiel2] (ver página 159 e 160) usa isso para garantir que apenas conteúdo carregado via HTTPS seja mostrado ao usuário; caso contrário, um alerta é exibido informando ao usuário que conteúdo misto foi detectado.

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

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

# nada encontrado

Neste caso, o aplicativo não faz uso disso.

Além disso, se você tiver o código-fonte original ou o IPA, pode inspecionar os arquivos HTML incorporados e verificar se eles não incluem conteúdo misto. Pesquise por http:// no código-fonte e dentro de atributos de tags, mas lembre-se de que isso pode gerar falsos positivos, pois, por exemplo, encontrar uma tag de âncora <a> que inclui um http:// em seu atributo href nem sempre apresenta um problema de conteúdo misto. Saiba mais sobre conteúdo misto na MDN Web Docs.

Teste para Manipulação de URI em WebView

Certifique-se de que o URI da WebView não possa ser manipulado pelo usuário para carregar outros tipos de recursos além dos necessários para o funcionamento da WebView. Isso pode ser especialmente perigoso quando o conteúdo da WebView é carregado do sistema de arquivos local, permitindo que o usuário navegue para outros recursos dentro do aplicativo.

Análise Dinâmica

Para a análise dinâmica, abordaremos os mesmos pontos da análise estática.

  • Enumeração de Instâncias de WebView
  • Teste se o JavaScript está Habilitado
  • Teste para Conteúdo Misto (Mixed Content)

É possível identificar WebViews e obter todas as suas propriedades em tempo de execução realizando instrumentação dinâmica. Isso é muito útil quando você não tem o código-fonte original.

Para os exemplos a seguir, continuaremos usando o aplicativo "Where's My Browser?" e o Frida REPL.

Enumeração de Instâncias de WebView

Uma vez identificada uma WebView no aplicativo, você pode inspecionar a heap para encontrar instâncias de uma ou várias das WebViews que vimos acima.

Por exemplo, se você usar o Frida, pode fazer isso inspecionando a heap via "ObjC.choose()"

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

Para as WebViews UIWebView e WKWebView, também imprimimos o URL associado para fins de completude.

Para garantir que você conseguirá encontrar as instâncias das WebViews na heap, certifique-se de primeiro navegar até a WebView que encontrou. Uma vez lá, execute o código acima, por exemplo, copiando no Frida REPL:

$ frida -U com.authenticationfailure.WheresMyBrowser

# copie o código e aguarde ...

onMatch:  <UIWebView: 0x14fd25e50; frame = (0 126; 320 393);
                autoresize = RM+BM; layer = <CALayer: 0x1c422d100>>
URL:  <NSMutableURLRequest: 0x1c000ef00> {
  URL: file:///var/mobile/Containers/Data/Application/A654D169-1DB7-429C-9DB9-A871389A8BAA/
          Library/UIWebView/scenario1.html, Method GET, Headers {
    Accept =     (
        "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
    );
    "Upgrade-Insecure-Requests" =     (
        1
    );
    "User-Agent" =     (
        "Mozilla/5.0 (iPhone; CPU iPhone ... AppleWebKit/604.3.5 (KHTML, like Gecko) Mobile/..."
    );
} }

Agora saímos com q e abrimos outra WebView (WKWebView neste caso). Ela também é detectada se repetirmos os passos anteriores:

$ frida -U com.authenticationfailure.WheresMyBrowser

# copie o código e aguarde ...

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

Vamos estender este exemplo nas seções seguintes para obter mais informações das WebViews. Recomendamos armazenar este código em um arquivo, por exemplo, webviews_inspector.js e executá-lo assim:

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

Verificando se o JavaScript está Habilitado

Lembre-se de que, se um UIWebView estiver sendo usado, o JavaScript está habilitado por padrão e não há possibilidade de desativá-lo.

Para WKWebView, você deve verificar se o JavaScript está habilitado. Use javaScriptEnabled de WKPreferences para isso.

Estenda o script anterior com a seguinte linha:

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

A saída mostra agora que, de fato, o JavaScript está habilitado:

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

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

javaScriptEnabled:  true

Teste para Conteúdo Misto (Mixed Content)

A classe UIWebView não fornece um método para verificar que apenas conteúdo seguro é permitido. No entanto, a partir do iOS 10, a diretiva CSP (Política de Segurança de Conteúdo) Upgrade-Insecure-Requests foi introduzida no WebKit, o mecanismo de navegador que alimenta as WebViews do iOS. Esta diretiva pode ser usada para instruir o navegador a atualizar solicitações inseguras para solicitações seguras. Esta é uma boa prática para evitar problemas de conteúdo misto.

Para WKWebViews, você pode chamar o método hasOnlySecureContent para cada um dos WKWebViews encontrados na heap. Lembre-se de fazer isso uma vez que a WebView tenha carregado.

Estenda o script anterior com a seguinte linha:

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

A saída mostra que alguns dos recursos na página foram carregados por meio de conexões inseguras:

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

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

hasOnlySecureContent:  false