MASTG-TEST-0070: 测试通用链接
此测试即将更新
此测试目前可使用,但将作为新的 OWASP MASTG v2 指南 的一部分进行全面修订。
通过提交 PR 来帮助我们:MASTG v1->v2 MASTG-TEST-0070:测试通用链接 (ios)
概述¶
静态分析¶
在静态方法上测试通用链接包括执行以下操作
- 检查关联域名授权
- 检索 Apple App Site Association 文件
- 检查链接接收器方法
- 检查数据处理程序方法
- 检查应用是否调用其他应用的通用链接
检查关联域名授权¶
通用链接要求开发者添加关联域名授权,并在其中包含应用支持的域名列表。
在 Xcode 中,转到 Capabilities 选项卡,然后搜索 Associated Domains。您还可以检查 .entitlements
文件,查找 com.apple.developer.associated-domains
。每个域名必须以 applinks:
作为前缀,例如 applinks:www.mywebsite.com
。
这是来自 Telegram 的 .entitlements
文件的示例
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:telegram.me</string>
<string>applinks:t.me</string>
</array>
更多详细信息可以在 已存档的 Apple 开发者文档 中找到。
如果您没有原始源代码,您可以从 MachO 文件中提取它们,如 从 MachO 二进制文件中提取授权 中所述。
检索 Apple App Site Association 文件¶
尝试使用您从上一步获得的关联域名从服务器检索 apple-app-site-association
文件。此文件需要通过 HTTPS 访问,没有任何重定向,位于 https://<domain>/apple-app-site-association
或 https://<domain>/.well-known/apple-app-site-association
。
您可以使用浏览器自行检索它,并导航到 https://<domain>/apple-app-site-association
、https://<domain>/.well-known/apple-app-site-association
或使用 Apple 的 CDN,网址为 https://app-site-association.cdn-apple.com/a/v1/<domain>
。
或者,您可以使用 Apple App Site Association (AASA) 验证器。输入域名后,它将显示该文件,为您验证它并显示结果(例如,它是否未通过 HTTPS 正确提供)。请参阅来自 apple.com https://www.apple.com/.well-known/apple-app-site-association
的以下示例
{
"activitycontinuation": {
"apps": [
"W74U47NE8E.com.apple.store.Jolly"
]
},
"applinks": {
"apps": [],
"details": [
{
"appID": "W74U47NE8E.com.apple.store.Jolly",
"paths": [
"NOT /shop/buy-iphone/*",
"NOT /us/shop/buy-iphone/*",
"/xc/*",
"/shop/buy-*",
"/shop/product/*",
"/shop/bag/shared_bag/*",
"/shop/order/list",
"/today",
"/shop/watch/watch-accessories",
"/shop/watch/watch-accessories/*",
"/shop/watch/bands",
] } ] }
}
“applinks”内的“details”键包含一个 JSON 表示形式的数组,该数组可能包含一个或多个应用。“appID”应与应用的授权中的“application-identifier”键匹配。接下来,使用“paths”键,开发者可以指定要在每个应用的基础上处理的特定路径。某些应用(如 Telegram)使用独立的 *("paths": ["*"]
),以便允许所有可能的路径。只有在某些网站的特定区域 不应 由某些应用处理时,开发者才能通过在相应路径前加上 "NOT "
(注意 T 后的空格)来限制访问。另请记住,系统将按照数组中字典的顺序查找匹配项(第一个匹配项获胜)。
此路径排除机制不应被视为安全功能,而应被视为开发者可能用来指定哪些应用打开哪些链接的过滤器。默认情况下,iOS 不会打开任何未验证的链接。
请记住,通用链接验证发生在安装时。iOS 检索其 com.apple.developer.associated-domains
授权中声明的域名 (applinks
) 的 AASA 文件。如果验证未成功,iOS 将拒绝打开这些链接。验证失败的一些原因可能包括
- AASA 文件不是通过 HTTPS 提供的。
- AASA 不可用。
appID
不匹配(这将是 恶意 应用的情况)。iOS 将成功阻止任何可能的劫持攻击。
检查链接接收器方法¶
为了接收链接并正确处理它们,应用委托必须实现 application:continueUserActivity:restorationHandler:
。如果您有原始项目,请尝试搜索此方法。
请注意,如果应用使用 openURL:options:completionHandler:
打开应用网站的通用链接,则链接不会在应用中打开。由于调用源自应用,因此不会将其作为通用链接处理。
来自 Apple Docs:当用户点击通用链接后 iOS 启动您的应用时,您将收到一个
NSUserActivity
对象,其activityType
值为NSUserActivityTypeBrowsingWeb
。activity 对象的webpageURL
属性包含用户正在访问的 URL。webpage URL 属性始终包含 HTTP 或 HTTPS URL,您可以使用NSURLComponents
API 来操作 URL 的组件。[...] 为了保护用户的隐私和安全,当您需要传输数据时,不应使用 HTTP;而应使用安全传输协议,例如 HTTPS。
从上面的注释中,我们可以突出显示
- 提到的
NSUserActivity
对象来自continueUserActivity
参数,如上面的方法中所示。 webpageURL
的方案必须是 HTTP 或 HTTPS(任何其他方案都应抛出异常)。URLComponents
/NSURLComponents
的scheme
实例属性 可用于验证这一点。
如果您没有原始源代码,则可以使用 radare2 for iOS 或 rabin2 来搜索链接接收器方法的二进制字符串
$ rabin2 -zq Telegram\ X.app/Telegram\ X | grep restorationHan
0x1000deea9 53 52 application:continueUserActivity:restorationHandler:
检查数据处理程序方法¶
您应该检查如何验证接收到的数据。Apple 明确警告了这一点
通用链接为您的应用提供了一个潜在的攻击媒介,因此请确保验证所有 URL 参数并丢弃任何格式错误的 URL。此外,将可用操作限制为那些不会危及用户数据的操作。例如,不允许通用链接直接删除内容或访问有关用户的敏感信息。测试 URL 处理代码时,请确保您的测试用例包括格式不正确的 URL。
如 Apple 开发者文档 中所述,当 iOS 因通用链接而打开应用时,该应用将收到一个 NSUserActivity
对象,其 activityType
值为 NSUserActivityTypeBrowsingWeb
。activity 对象的 webpageURL
属性包含用户访问的 HTTP 或 HTTPS URL。以下 Swift 示例在打开 URL 之前完全验证了这一点
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// ...
if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL {
application.open(url, options: [:], completionHandler: nil)
}
return true
}
此外,请记住,如果 URL 包含参数,则在仔细清理和验证之前不应信任它们(即使来自受信任的域)。例如,它们可能已被攻击者欺骗,或者可能包含格式错误的数据。如果是这种情况,则必须丢弃整个 URL,因此也必须丢弃通用链接请求。
NSURLComponents
API 可用于解析和操作 URL 的组件。这也可能是方法 application:continueUserActivity:restorationHandler:
本身的一部分,或者可能发生在从中调用的单独方法上。以下示例 演示了这一点
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
let path = components.path,
let params = components.queryItems else {
return false
}
if let albumName = params.first(where: { $0.name == "albumname" })?.value,
let photoIndex = params.first(where: { $0.name == "index" })?.value {
// Interact with album name and photo index
return true
} else {
// Handle when album and/or album name or photo index missing
return false
}
}
最后,如上所述,请务必验证 URL 触发的操作不会以任何方式泄露敏感信息或危及用户数据。
检查应用是否调用其他应用的通用链接¶
应用可能会通过通用链接调用其他应用,以便简单地触发某些操作或传输信息,在这种情况下,应验证它是否没有泄漏敏感信息。
如果您有原始源代码,则可以搜索 openURL:options: completionHandler:
方法并检查正在处理的数据。
请注意,
openURL:options:completionHandler:
方法不仅用于打开通用链接,还用于调用自定义 URL 方案。
这是来自 Telegram 应用的示例
}, openUniversalUrl: { url, completion in
if #available(iOS 10.0, *) {
var parsedUrl = URL(string: url)
if let parsed = parsedUrl {
if parsed.scheme == nil || parsed.scheme!.isEmpty {
parsedUrl = URL(string: "https://\(url)")
}
}
if let parsedUrl = parsedUrl {
return UIApplication.shared.open(parsedUrl,
options: [UIApplicationOpenURLOptionUniversalLinksOnly: true as NSNumber],
completionHandler: { value in completion.completion(value)}
)
请注意应用如何在打开之前将 scheme
调整为“https”,以及它如何使用 仅当 URL 是有效的通用链接且存在能够打开该 URL 的已安装应用时才打开 URL 的选项 UIApplicationOpenURLOptionUniversalLinksOnly: true
。
如果您没有原始源代码,请在应用的二进制文件的符号和字符串中搜索。例如,我们将搜索包含“openURL”的 Objective-C 方法
$ rabin2 -zq Telegram\ X.app/Telegram\ X | grep openURL
0x1000dee3f 50 49 application:openURL:sourceApplication:annotation:
0x1000dee71 29 28 application:openURL:options:
0x1000df2c9 9 8 openURL:
0x1000df772 35 34 openURL:options:completionHandler:
正如预期的那样,openURL:options:completionHandler:
是发现的方法之一(请记住,它也可能存在,因为应用打开了自定义 URL 方案)。接下来,为了确保没有泄漏敏感信息,您将必须执行动态分析并检查正在传输的数据。有关挂钩和跟踪此方法的一些示例,请参阅 测试自定义 URL 方案。
动态分析¶
如果应用实现了通用链接,您应该从静态分析中获得以下输出
- 关联域名
- Apple App Site Association 文件
- 链接接收器方法
- 数据处理程序方法
您现在可以使用此信息来动态测试它们
- 触发通用链接
- 识别有效通用链接
- 追踪链接接收器方法
- 检查链接的打开方式
触发通用链接¶
与自定义 URL 方案不同,不幸的是,您无法直接通过在搜索栏中键入通用链接来从 Safari 测试通用链接,因为 Apple 不允许这样做。但是您可以随时使用其他应用(如“备忘录”应用)测试它们
- 打开“备忘录”应用并创建新备忘录。
- 写入包含域名的链接。
- 离开“备忘录”应用中的编辑模式。
- 长按链接以打开它们(请记住,标准点击会触发默认选项)。
要从 Safari 执行此操作,您将必须在网站上找到一个现有链接,单击该链接后,它将被识别为通用链接。这可能有点耗时。
或者,您也可以为此使用 Frida,有关更多详细信息,请参阅 测试自定义 URL 方案。
识别有效通用链接¶
首先,我们将看到打开允许的通用链接和不应允许的通用链接之间的区别。
从上面看到的 apple.com 的 apple-app-site-association
中,我们选择了以下路径
"paths": [
"NOT /shop/buy-iphone/*",
...
"/today",
其中一个应提供“在应用中打开”选项,而另一个不应。
如果我们长按第一个链接 (http://www.apple.com/shop/buy-iphone/iphone-xr
),它只会提供打开它的选项(在浏览器中)。
如果我们长按第二个链接 (http://www.apple.com/today
),它会显示在 Safari 和“Apple Store”中打开它的选项
请注意,点击和长按之间存在差异。一旦我们长按链接并选择一个选项(例如,“在 Safari 中打开”),这将成为所有未来点击的默认选项,直到我们再次长按并选择另一个选项。
如果我们在 application:continueUserActivity: restorationHandler:
方法上重复该过程(通过挂钩或跟踪),我们将看到一旦我们打开允许的通用链接,它就会被调用。为此,您可以使用例如 frida-trace
frida-trace -U "Apple Store" -m "*[* *restorationHandler*]"
追踪链接接收器方法¶
本节介绍如何追踪链接接收器方法以及如何提取其他信息。在此示例中,我们将使用 Telegram,因为其 apple-app-site-association
文件中没有任何限制
{
"applinks": {
"apps": [],
"details": [
{
"appID": "X834Q8SBVP.org.telegram.TelegramEnterprise",
"paths": [
"*"
]
},
{
"appID": "C67CF9S4VU.ph.telegra.Telegraph",
"paths": [
"*"
]
},
{
"appID": "X834Q8SBVP.org.telegram.Telegram-iOS",
"paths": [
"*"
]
}
]
}
}
为了打开链接,我们还将使用“备忘录”应用和 frida-trace,使用以下模式
frida-trace -U Telegram -m "*[* *restorationHandler*]"
写入 https://t.me/addstickers/radare
(通过快速 Internet 搜索找到)并从“备忘录”应用中打开它。
首先,我们让 frida-trace 在 __handlers__/
中生成存根
$ frida-trace -U Telegram -m "*[* *restorationHandler*]"
Instrumenting functions...
-[AppDelegate application:continueUserActivity:restorationHandler:]
您可以看到只找到一个函数并且正在进行检测。现在触发通用链接并观察跟踪。
298382 ms -[AppDelegate application:0x10556b3c0 continueUserActivity:0x1c4237780
restorationHandler:0x16f27a898]
您可以观察到该函数实际上正在被调用。您现在可以将代码添加到 __handlers__/
中的存根以获取更多详细信息
// __handlers__/__AppDelegate_application_contin_8e36bbb1.js
onEnter: function (log, args, state) {
log("-[AppDelegate application: " + args[2] + " continueUserActivity: " + args[3] +
" restorationHandler: " + args[4] + "]");
log("\tapplication: " + ObjC.Object(args[2]).toString());
log("\tcontinueUserActivity: " + ObjC.Object(args[3]).toString());
log("\t\twebpageURL: " + ObjC.Object(args[3]).webpageURL().toString());
log("\t\tactivityType: " + ObjC.Object(args[3]).activityType().toString());
log("\t\tuserInfo: " + ObjC.Object(args[3]).userInfo().toString());
log("\trestorationHandler: " +ObjC.Object(args[4]).toString());
},
新输出是
298382 ms -[AppDelegate application:0x10556b3c0 continueUserActivity:0x1c4237780
restorationHandler:0x16f27a898]
298382 ms application:<Application: 0x10556b3c0>
298382 ms continueUserActivity:<NSUserActivity: 0x1c4237780>
298382 ms webpageURL:http://t.me/addstickers/radare
298382 ms activityType:NSUserActivityTypeBrowsingWeb
298382 ms userInfo:{
}
298382 ms restorationHandler:<__NSStackBlock__: 0x16f27a898>
除了函数参数之外,我们还通过从它们调用一些方法来添加更多信息以获取更多详细信息,在本例中是关于 NSUserActivity
。如果我们查看 Apple 开发者文档,我们可以看到我们还可以从此对象调用哪些内容。
检查链接的打开方式¶
如果您想了解有关哪个函数实际打开 URL 以及实际如何处理数据的更多信息,您应该继续调查。
扩展以前的命令,以便找出是否有任何其他函数参与打开 URL。
frida-trace -U Telegram -m "*[* *restorationHandler*]" -i "*open*Url*"
-i
包括任何方法。您也可以在此处使用 glob 模式(例如,-i "*open*Url*"
表示“包括任何包含 'open',然后是 'Url' 和其他内容的函数”)
同样,我们首先让 frida-trace 在 __handlers__/
中生成存根
$ frida-trace -U Telegram -m "*[* *restorationHandler*]" -i "*open*Url*"
Instrumenting functions...
-[AppDelegate application:continueUserActivity:restorationHandler:]
$S10TelegramUI0A19ApplicationBindingsC16openUniversalUrlyySS_AA0ac4OpenG10Completion...
$S10TelegramUI15openExternalUrl7account7context3url05forceD016presentationData18application...
$S10TelegramUI31AuthorizationSequenceControllerC7account7strings7openUrl5apiId0J4HashAC0A4Core19...
...
现在您可以看到一个长函数列表,但我们仍然不知道将调用哪些函数。再次触发通用链接并观察跟踪。
/* TID 0x303 */
298382 ms -[AppDelegate application:0x10556b3c0 continueUserActivity:0x1c4237780
restorationHandler:0x16f27a898]
298619 ms | $S10TelegramUI15openExternalUrl7account7context3url05forceD016presentationData
18applicationContext20navigationController12dismissInputy0A4Core7AccountC_AA
14OpenURLContextOSSSbAA012PresentationK0CAA0a11ApplicationM0C7Display0
10NavigationO0CSgyyctF()
除了 Objective-C 方法之外,现在还有一个 Swift 函数也是您感兴趣的。
可能没有关于该 Swift 函数的文档,但您可以简单地使用 xcrun
通过 swift-demangle
解构其符号
xcrun 可用于从命令行调用 Xcode 开发者工具,而无需将它们放在路径中。在这种情况下,它将找到并运行 swift-demangle,这是一个解构 Swift 符号的 Xcode 工具。
$ xcrun swift-demangle S10TelegramUI15openExternalUrl7account7context3url05forceD016presentationData
18applicationContext20navigationController12dismissInputy0A4Core7AccountC_AA14OpenURLContextOSSSbAA0
12PresentationK0CAA0a11ApplicationM0C7Display010NavigationO0CSgyyctF
导致
---> TelegramUI.openExternalUrl(
account: TelegramCore.Account, context: TelegramUI.OpenURLContext, url: Swift.String,
forceExternal: Swift.Bool, presentationData: TelegramUI.PresentationData,
applicationContext: TelegramUI.TelegramApplicationContext,
navigationController: Display.NavigationController?, dismissInput: () -> ()) -> ()
这不仅为您提供了方法的类(或模块)、其名称和参数,还揭示了参数类型和返回类型,因此如果您需要更深入地研究,现在您知道从哪里开始。
现在我们将使用此信息通过编辑存根文件来正确打印参数
// __handlers__/TelegramUI/_S10TelegramUI15openExternalUrl7_b1a3234e.js
onEnter: function (log, args, state) {
log("TelegramUI.openExternalUrl(account: TelegramCore.Account,
context: TelegramUI.OpenURLContext, url: Swift.String, forceExternal: Swift.Bool,
presentationData: TelegramUI.PresentationData,
applicationContext: TelegramUI.TelegramApplicationContext,
navigationController: Display.NavigationController?, dismissInput: () -> ()) -> ()");
log("\taccount: " + ObjC.Object(args[0]).toString());
log("\tcontext: " + ObjC.Object(args[1]).toString());
log("\turl: " + ObjC.Object(args[2]).toString());
log("\tpresentationData: " + args[3]);
log("\tapplicationContext: " + ObjC.Object(args[4]).toString());
log("\tnavigationController: " + ObjC.Object(args[5]).toString());
},
这样,下次我们运行它时,我们会得到更详细的输出
298382 ms -[AppDelegate application:0x10556b3c0 continueUserActivity:0x1c4237780
restorationHandler:0x16f27a898]
298382 ms application:<Application: 0x10556b3c0>
298382 ms continueUserActivity:<NSUserActivity: 0x1c4237780>
298382 ms webpageURL:http://t.me/addstickers/radare
298382 ms activityType:NSUserActivityTypeBrowsingWeb
298382 ms userInfo:{
}
298382 ms restorationHandler:<__NSStackBlock__: 0x16f27a898>
298619 ms | TelegramUI.openExternalUrl(account: TelegramCore.Account,
context: TelegramUI.OpenURLContext, url: Swift.String, forceExternal: Swift.Bool,
presentationData: TelegramUI.PresentationData, applicationContext:
TelegramUI.TelegramApplicationContext, navigationController: Display.NavigationController?,
dismissInput: () -> ()) -> ()
298619 ms | account: TelegramCore.Account
298619 ms | context: nil
298619 ms | url: http://t.me/addstickers/radare
298619 ms | presentationData: 0x1c4e40fd1
298619 ms | applicationContext: nil
298619 ms | navigationController: TelegramUI.PresentationData
在那里您可以观察到以下内容
- 它按预期从应用委托中调用
application:continueUserActivity:restorationHandler:
。 application:continueUserActivity:restorationHandler:
处理 URL 但不打开它,它为此调用TelegramUI.openExternalUrl
。- 正在打开的 URL 是
https://t.me/addstickers/radare
。
您现在可以继续尝试跟踪和验证数据的验证方式。例如,如果您有两个通过通用链接通信的应用,您可以使用此方法通过在接收应用中挂钩这些方法来查看发送应用是否正在泄漏敏感数据。当您没有源代码时,这特别有用,因为您将能够检索完整的 URL,否则您将无法看到它,因为它可能是单击某些按钮或触发某些功能的结果。
在某些情况下,您可能会在 NSUserActivity
对象的 userInfo
中找到数据。在前面的示例中,没有传输任何数据,但对于其他情况,这可能是这种情况。要查看这一点,请务必挂钩 userInfo
属性或直接从挂钩中的 continueUserActivity
对象访问它(例如,通过添加像这样的行 log("userInfo:" + ObjC.Object(args[3]).userInfo().toString());
)。
关于通用链接和 Handoff 的最终说明¶
通用链接和 Apple 的 Handoff 功能 是相关的
- 两者在接收数据时都依赖于相同的方法
application:continueUserActivity:restorationHandler:
- 与通用链接一样,Handoff 的活动延续必须在
com.apple.developer.associated-domains
授权中以及服务器的apple-app-site-association
文件中声明(在两种情况下都通过关键字"activitycontinuation":
)。有关示例,请参见上面的“检索 Apple App Site Association 文件”。
实际上,“检查链接的打开方式”中的上一个示例与 "Handoff 编程指南" 中描述的“Web 浏览器到原生应用 Handoff”方案非常相似
如果用户正在发起设备上使用 Web 浏览器,并且接收设备是具有声明
webpageURL
属性的域部分的原生应用的 iOS 设备,则 iOS 启动原生应用并向其发送一个NSUserActivity
对象,其activityType
值为NSUserActivityTypeBrowsingWeb
。webpageURL
属性包含用户正在访问的 URL,而userInfo
字典为空。
在上面的详细输出中,您可以看到我们收到的 NSUserActivity
对象完全满足提到的要点
298382 ms -[AppDelegate application:0x10556b3c0 continueUserActivity:0x1c4237780
restorationHandler:0x16f27a898]
298382 ms application:<Application: 0x10556b3c0>
298382 ms continueUserActivity:<NSUserActivity: 0x1c4237780>
298382 ms webpageURL:http://t.me/addstickers/radare
298382 ms activityType:NSUserActivityTypeBrowsingWeb
298382 ms userInfo:{
}
298382 ms restorationHandler:<__NSStackBlock__: 0x16f27a898>
此知识应在测试支持 Handoff 的应用时为您提供帮助。