使用Diligence 模糊测试重现DeusDao 漏洞

phantom

版主

phantom Rep
0
0
0
Rep
0
phantom Vouches
Vouches
0
8 MONTHS
8 8 MONTHS OF SERVICE
phantom 获得
0
0
0
获得
0
phantom 凭证
0
0
0
凭证
0
主题
27
点赞
1
HakB
8
在社区成长 8 月
LEVEL 90 100 XP
 
2023 年5 月6 日,DeusDao 被利用,造成650 万美元的损失。可以在此处找到该事件的详细记录。该漏洞利用的根本原因是burnFrom 函数中的逻辑错误。
function burnFrom(地址账户, uint256金额) public virtual {
uint256 currentAllowance=_allowances[_msgSender()][帐户];
_approve(账户, _msgSender(), currentAllowance - 金额);
_burn(账户, 金额);
}
在burnFrom的第一行,在计算要燃烧的代币限额时,消息发送者和帐户被意外交换。因此,使用的是消息发送者的账户限额,而不是消息发送者的账户限额。攻击者可以通过首先调用某个受害者地址的approve(victim, N),然后调用burnFrom(victim, 0)来利用此漏洞。因此,由于在burnFrom 第二行调用_approve,攻击者将被批准花费N 个受害者的代币。
: 可以防止此类漏洞
为代码应该做什么编写详细的规范
检查代码是否符合规范
在这篇文章中,我们将演示如何通过使用Scribble 编写规范并使用Diligence Fuzzing 检查该规范来实现这一点。

开始使用​

您可以从这里获取DeusDao 的代码。请注意,在此版本中,该漏洞已得到修复。因此,首先我们需要重新引入该漏洞,将contracts/ERC20/ERC20Custom.sol中的burnFrom主体替换为上一节中所示的易受攻击的实现。
接下来你将需要:
Diligence Fuzzing 帐户(您可以获得免费试用帐户)
Scribble(您可以通过运行npm i eth-scribble 来安装它)
Diligence Fuzzing CLI(您可以通过运行pip install Diligence-fuzzing 来安装它)

编写规范​

表达被侵犯的安全属性的一种方式是:
“只有所有者才能增加某人使用其代币的限额”
Scribble 允许我们轻松地将这个属性表达为内部_approve 方法上的注释:
///#if_succeeds '只有所有者可以增加津贴'
///old(_allowances[所有者][spender]) _allowances[所有者][spender]==msg.sender==所有者;
function _approve(地址所有者,地址支出者,uint256金额)内部虚拟{
require(owner !=address(0), 'ERC20: 从零地址批准');
require(spender !=address(0), 'ERC20: 批准零地址');
_allowances[所有者][支出者]=金额;
发出批准(所有者、支出者、金额);
}
该属性表示为函数上方的文档字符串,以#if_succeeds 关键字开头。该属性包括用户可读的描述,以及表示为谓词:的实际属性
old(_allowances[所有者][花费者]) _allowances[所有者][花费者]==msg.sender==所有者
该谓词精确地捕捉了我们之前用英语表达的安全属性。有关Scribble 规格的更多信息,请参阅文档。

检查规格​

即使只是写下正式规范也是一项有用的练习,它可以提高代码质量并经常导致发现错误。然而,如果我们实际检查代码是否遵循规范,那么规范会更有用。有很多方法可以做到这一点,从手动检查到自动和半自动验证。
我们将使用Scribble + Diligence Fuzzing 来检查底层代码是否符合规范,方法是自动将其转换为插入代码中的可执行检查,然后对检测的代码进行模糊测试。这有几个步骤。

生成配置​

首先,我们需要通过运行fuzz configgenerate 来生成模糊测试配置。 CLI 工具将询问我们几个有用的问题,例如代码所在的位置以及我们正在使用什么样的工具链。之后它将把配置存储在.fuzz.yml 中。我们还需要将模糊测试api 密钥添加到配置中(您可以在登录后从模糊测试仪表板生成一个):
key:“……”

检测规范​

接下来我们需要将规范转换为可执行检查。这是通过运行: 来完成的
$ 毛毛臂
如果您好奇,请查看contracts/ERC20/ERC20Custom.sol 中的_approve 函数,看看我们的工具所做的更改。您始终可以通过运行fuzz disarm 来撤消所有这些更改。

生成种子状态​

检测后,我们需要生成用于模糊测试的种子状态。我们通过编写一个小脚本来将我们想要模糊测试的合约部署到本地节点来实现这一点。例如我们使用下面的脚本:
const { setBalance }=require('./helpers/modify_chain.js');
异步函数main() {
const 部署者=等待hre.ethers.getSigner();
const deployerAddr=deployer.address;
const trustyAddress=部署者地址;
等待setBalance(deployerAddr);
const ContractInstance=等待hre.ethers.getContractFactory(
'DEIS稳定币',
部署者
);
const 合约=等待contractInstance.deploy('DEI', 'DEI', trustyAddress);
等待合约.部署();
控制台.错误(
`DEIStablecoin 由${deployer.address} 部署到${contract.address}`
);
返回;
}
主要()
.then(()=process.exit(0))
.catch((错误)={
控制台.错误(错误);
进程.退出(1);
});
现在我们可以启动本地ganache 或Hardhat 节点,并通过运行: 将我们的合约部署到该节点
$ npx hardhat run --network localhost script/deploy.js
.
DEIStablecoin 由0x7ef39802d1949909b21eDB40392e88B46AEa6c7d 部署到0x0C1Ad3a67Ad77f99ac643B230D56093f3Cf2A54C
本地节点的状态就是我们所说的种子状态。此时我们几乎准备好进行模糊测试了。我们只需要将目标合约部署的地址放入配置文件中的deployment_contract_address字段即可:
# 以下地址将成为模糊测试活动的主要目标
Deployed_contract_address:'0x0C1Ad3a67Ad77f99ac643B230D56093f3Cf2A54C'

开展活动​

现在我们准备通过运行: 开始我们的活动
$ 模糊运行
您可以在此处查看活动: https://fuzzing.diligence.tools/campaigns/cmp_.
如果我们点击链接,我们将被引导至该项目的Diligence Fuzzing 仪表板。一段时间后(在我的例子中大约20 分钟),模糊器将发现对您表达的财产的侵犯:
Untitled

单击该位置即可进入代码: 中的检测检查
Untitled

在检查检测线下方,仪表板为我们提供了有关违规的更多信息。最值得注意的是,它显示了模糊器违反我们规范所采取的确切步骤。在这种情况下,这些正是攻击者采取的步骤:
批准一些受害者合约以获得一定数量的代币(通过调用increaseAllownce)
使用相同的受害者合约调用burnFrom
因此,在给定简单的Scribble 规范的情况下,模糊器能够发现导致DeusDao 漏洞的漏洞。

结论​

为合同编写正式规范,并检查合同是否正确实施规范是一种强大的开发实践。 Diligence Fuzzing 允许您利用Scribble 属性作为规范的一种形式来做到这一点。如本文所示,此技术可用于增强代码以抵御现实世界的攻击。
考虑智能合约安全吗?我们可以提供培训、持续建议和智能合约审计。联系我们。
 
顶部