我们刚刚看到一个小漏洞如何导致(不同程度的)财务损失,就像基于 Solidity 开发的智能合约容易受到各种已知和未知攻击一样。 攻击者利用漏洞和漏洞窥探智能合约并操纵它们进行攻击。 在这里,我们列出了 Solidity 编程语言中最常见的 5 个错误。
在 QuillAudits,我们遵循自适应方法来了解每一次黑客攻击的要点,并在未来的智能合约中实施其学习,以避免任何潜在威胁。
Solidity 编程语言中的错误
1. 未经检查的外部呼叫
我们首先提出这个问题,因为它是最常见的 Solidity 陷阱之一。 通常,将以太币发送到任何外部帐户是通过 转移() 功能。 除此之外,进行外部调用的两个最广泛使用的函数是: 称呼()及 发送(),这里主要是 称呼() 函数被开发人员广泛用于执行多种外部调用。
虽然 称呼() 和 发送() 函数返回一个布尔值,指定调用是否成功。 因此在这种情况下,如果任何函数 称呼() or 发送() 未能执行任务,他们将返回 假。 因此,如果开发人员不交叉检查返回值,它将成为一个陷阱。
漏洞
考虑下面的例子:
合约乐透{
boolpublic payedOut =假;
向公众获胜者致辞;
uintpublic winAmount;
// … 这里有额外的功能
函数 sendToWinner()public{
要求(!payedOut);
获胜者.send(winAmount);
已付清=真;
}
函数withdrawLeftOver()public{
要求(已支付);
msg.sender.send(this.balance);
}
}
在上面的类似乐透的合约中,我们可以观察到 优胜者 接收 赢额 留下一点点剩余的以太可以从任何外部代理中取出。
在这里,合约的陷阱存在于第 [11] 行,其中 提交 在没有交叉验证响应的情况下使用。 在上面的例子中,一个 优胜者 其交易失败(由于 Gas 不足或合同有意加入回退功能),授权 已支付 设置为 true 无论以太币交易是否成功。 在这种情况下,任何剥削者都可以撤回 优胜者 奖金通过 撤回LeftOver 功能。
QuillAudit 的方法
我们的内部开发团队使用 [转移] 函数而不是 [发送] 功能,因为如果外部交易恢复,[transfer] 将恢复。 如果您使用 [send],请始终交叉检查返回值。
我们遵循的稳健方法之一是利用 [提款模式]。 在这里,我们在逻辑上将外部发送功能与代码库的其余部分隔离开来,并将潜在失败交易的压力放在最终用户身上,因为他是调用提款功能的人。
2. 重入
以太坊智能合约调用并利用其他外部合约的代码,为此,合约需要提交外部调用。 这些外部调用很容易受到攻击,最近在 DAO hack 的情况下发生了一种这样的攻击。
漏洞
当合约将以太币发送到未知地址时,攻击者就会进行此类攻击。 在这种情况下,攻击者可以在回退函数中拥有恶意代码的外部地址创建合约,当合约向该地址发送以太币时,该恶意代码将被调用。
事实: 术语“可重入性”源于这样一个事实:当外部恶意合约通过易受攻击的合约调用函数,然后代码执行路径“重新进入”它。
考虑下面的例子,它是一个以太坊保险库,允许存款人每周仅提取 1 个以太币。
合约 EtherStore {
uint256 公共提款限额 = 1 以太币;
映射(地址 => uint256)公共 lastWithdrawTime;
映射(地址 => uint256)公共余额;
函数 depositFunds() 外部应付 {
余额[msg.sender] += msg.value;
}
函数withdrawFunds(uint256 _weiToWithdraw)公共{
要求(余额[msg.sender] >= _weiToWithdraw);
// 限制提现
要求(_weiToWithdraw <= 取款限制);
// 限制允许提现的时间
要求(现在 >= lastWithdrawTime[msg.sender] + 1 周);
要求(msg.sender.call.value(_weiToWithdraw)());
余额[msg.sender] -= _weiToWithdraw;
lastWithdrawTime[msg.sender] = 现在;
}
}
在上面的合约中,我们有两个公共函数,[depositFunds] 和 [withdrawFunds]。 [depositFunds] 用于增加发送方的余额,而 [withdrawFunds] 指定要提取的金额。 在这种情况下,如果要提取的数量少于 1 个以太币,就会成功。
这里的陷阱在于以太转移发生的第 [17] 行。 攻击者可以使用 [EtherStores] 的合约地址作为唯一的构造函数参数来创建恶意合约。 这将使 [etherStore] 成为公共变量,因此更容易受到攻击。
QuillAudit 的方法
我们遵循各种技术来避免智能合约中潜在的可重入漏洞。 第一个也是最好的方法是在将以太币转移到任何外部合约时使用内置的 [transfer] 函数。
其次,重要的是要确保状态变量中的所有逻辑更改都应该在将以太币从合约中发送出去之前完成。 在 [EtherStore] 示例中,行 [18] 和 [19] 应放在行 [17] 之前。
第三种技术也可用于防止重入调用; 通过引入互斥锁。 它是一个附加的状态变量,将在代码执行期间锁定合约。
3. 默认可见性
我们在 Solidity 中使用的函数有可见性说明符,它们规定了调用它们的方式。 可见性决定了函数的调用; 外部由用户,由其他衍生合约,仅在内部或仅在外部。 让我们看看错误使用可见性说明符如何导致智能合约中的巨大漏洞。
漏洞
默认情况下,函数的可见性是 [public],因此外部用户可以调用没有特定可见性的函数。 当开发人员忘记指定应该是私有的(或可以在合约本身内调用的)函数的可见性时,就会出现错误。 例如;
合约 HashForEther {
函数withdrawWinnings() {
// 如果地址的最后 8 个十六进制字符为 0,则获胜者
要求(uint32(msg.sender)== 0);
_sendWinnings();
}
函数_sendWinnings(){
msg.sender.transfer(this.balance);
}
}
上述合约是一个简单的地址猜测赏金游戏。 在这里,我们可以看到没有指定函数的可见性,特别是 [_sendWinnings] 函数是 [public](默认情况下),因此可以通过任何地址调用它来窃取赏金。
QuillAudit 的方法
我们的内部团队由经验丰富的开发人员组成,他们始终遵循最佳审计实践,此处应明确指定功能的可见性,即使它们要公开,也应提及。
4. 保护构造函数的使用
通常,构造函数被称为特殊函数,用于在初始化合约时执行关键和特权任务。 在 Solidity [v0.4.22] 之前,构造函数的名称与包含它们的合约使用的名称相同。 现在,考虑在开发阶段更改合约名称但构造函数名称保持不变的情况,此漏洞还可以为攻击者提供轻松进入您的智能合约的机会。
漏洞
如果修改了合约名称而构造函数的名称不变,则会导致严重的后果。 例如:
合约 OwnerWallet {
地址公共所有者;
// 构造函数
功能所有者钱包(地址_owner)公共{
所有者=_所有者;
}
// 倒退。 收集乙醚。
函数()应付{}
功能撤回()公共{
要求(msg.sender == 所有者);
msg.sender.transfer(this.balance);
}
}
在上面的合约中,我们可以看到只有所有者才能通过调用 [withdraw] 函数来提取以太币。 在这里,由于构造函数的名称与合约不同(第一个字母不同!),因此出现漏洞。 这样exploiter就可以调用[ownerWallet]函数,将自己授权为owner,然后调用[withdraw]取出合约中的所有以太币。
QuillAudit 的方法
我们遵守 Solidity 编译器的 [0.4.22] 版本。 这个版本引入了一个关键字; [constructor] 要求函数名称与合约名称相匹配。
5. Tx.Origin 认证
这里,[Tx.Origin] 是 Solidity 的全局变量,它包含最初执行调用或交易的账户地址。 此变量不能用于身份验证,因为这样做会使合约容易受到网络钓鱼攻击。
漏洞
通过 [tx.origin] 变量授权用户的合约会受到外部攻击,导致用户对错误的合约执行经过身份验证的操作。 考虑下面的例子:
合同钓鱼{
地址公共所有者;
构造函数(地址_owner){
所有者=_所有者;
}
function () external pay {} // 收集以太币
功能withdrawAll(地址_recipient)公共{
需要(tx.origin == 所有者);
_recipient.transfer(this.balance);
}
}
在第 [11] 行,合约在 [tx.origin] 的帮助下授权 [withdrawAll] 功能。
QuillAudit 的方法
我们通常避免在智能合约中使用 [tx.origin] 进行授权。 虽然 [tx.origin] 的使用并未被严格禁止,但它有一些特定的用例。 我们可以使用 [tx.origin] 来拒绝外部合约调用当前合约,它可以使用 [require(tx.origin == msg.sender)] 形式的 [require] 执行。 这样做是为了避免调用中间合约来调用当前合约,这将合约限制为常规的无代码地址。
最后总结
我们已经全面涵盖了 Solidity 语言中的五个常见陷阱。 在开发智能合约时,我们不能忘记它们在设计上是不可变的,这意味着一旦我们创建了它们,就无法修补源代码。
这对开发人员在部署前利用可用的安全测试和审计工具提出了巨大挑战。
发现对智能合约的潜在恶意威胁,以及我们上面提到的一些风险,我们的内部审计专家团队以非常独特和强大的方式执行。 我们 QuillAudits 尽最大努力进行安全研究,以通过所有软件安全实践更新您的合同,以确保您的合同安全可靠。
到达QuillHash
拥有多年行业经验, 羽毛笔散列 在全球范围内提供了企业解决方案。 QuillHash与专家团队是一家领先的区块链开发公司,提供包括DeFi企业在内的各种行业解决方案,如果您在智能合约审计中需要任何帮助,请随时与我们的专家联系 在这里!
关注QuillHash了解更多更新
来源:https://blog.quillhash.com/2021/06/04/top-5-common-errors-in-solidity-programming-language/