使用Crytic进行漏洞狩猎 - 发现智能合约中的关键安全问题

本文详细介绍了Crytic工具在多个知名区块链项目中发现的12个安全漏洞,包括允许免费铸造代币、重入攻击等高风险问题,展示了自动化安全检测工具在智能合约审计中的强大能力。

Bug Hunting with Crytic - The Trail of Bits Blog

Crytic是我们开发的GitHub应用程序,用于发现智能合约漏洞:它能在无需人工干预的情况下检测安全问题,在开发过程中提供持续保障,并在部署前保护您的代码库。

Crytic能发现许多其他工具无法检测的漏洞,包括一些尚未广为人知的问题。目前Crytic拥有90多个检测器,我们还在持续添加新检查并改进现有功能。它会在每次提交时运行这些漏洞和优化检测器,并评估您自己添加的自定义安全属性!

今天我们分享12个由Crytic单独发现的主要项目问题,其中包括一些高严重性漏洞。

发现的问题清单

  1. 未使用的返回值可能允许免费铸造代币
  2. registerVerificationKey总是返回空字节
  3. MerkleTree.treeHeight和MerkleTree.zero可以设为常量
  4. 修饰符可能返回默认值
  5. 危险的严格相等可能使合约被锁定
  6. ABI encodePacked碰撞
  7. 缺少继承容易出错
  8. Msg.value在fundWithAward中被使用了两次
  9. 重入可能导致代币被盗
  10. [待确认]
  11. [待确认]
  12. [待确认]

Ernst & Young Nightfall

Crytic在E&Y的Nightfall项目中发现了三个漏洞,包括一个可能允许任何人免费铸造代币的关键漏洞。

问题1:未使用的返回值可能允许铸造免费代币

描述
FTokenShield.mint没有检查transferFrom的返回值。如果FTokenShield与在错误转账时不回滚而只返回false的代币(如BAT)一起使用,任何人都可以免费铸造承诺——攻击者可以免费铸造代币。

FTokenShield.burn中存在一个影响较小的类似问题:这里没有检查fToken.transfer。

建议
检查transferFrom和transfer的返回值。

问题2:registerVerificationKey总是返回空字节

描述
FTokenShield.registerVerificationKey和NFTokenShield.registerVerificationKey返回一个空的bytes32。不清楚正确的返回值应该是什么。这可能导致第三方和合约出现意外行为。

建议
考虑返回一个值,或者从签名中移除返回值。

问题3:MerkleTree.treeHeight和MerkleTree.zero可以设为常量

描述
treeHeight和zero可以在MerkleTree.sol中声明为常量,以允许编译器优化此代码。

建议
永不改变的状态变量可以声明为常量以节省gas。

DeFiStrategies

Crytic在DeFiStrategies中发现了一个不寻常的问题:修饰符中缺少占位符执行导致调用函数返回默认值。此外,Crytic还发现了一个与balanceOf调用返回值的严格相等相关的问题。

问题4:修饰符可能返回默认值

描述
SuperSaverZap.onlyInEmergency()和SuperSaverZap.stopInEmergency()修饰符在无效访问时不会回滚。如果修饰符不执行或回滚,函数执行将返回默认值,这可能会误导调用者。

建议
在两个修饰符中用require替换if()条件。

问题5:危险的严格相等可能使合约被锁定

描述
ERC20toUniPoolZapV1_General.addLiquidity对_UniSwapExchangeContractAddress余额使用了严格相等。这种行为可能允许攻击者通过向_UniSwapExchangeContractAddress发送代币来锁定合约。

建议
在比较中将==改为<=。

DOSNetwork

Crytic在DOSNetwork中发现了一个不太为人所知的问题:如果用多个动态参数调用abi.encodedPacked,它可能返回不同参数的相同值。

问题6:ABI encodePacked碰撞

描述
DOSProxy使用encodePacked Solidity函数处理两个连续的字符串(dataSource和selector):eth-contracts/contracts/DOSProxy.sol。

这种模式容易发生碰撞,其中具有不同dataSource和selector的两个调用可能导致相同的queryId(更多信息请参阅Solidity文档)。

建议
不要在encodePacked中使用多个动态类型,考虑先用keccak256对dataSource和selector进行哈希处理。

ethereum-oasis/Baseline

Crytic在Baseline协议中发现了一个架构问题:实现接口的合约没有从接口继承。

问题7:缺少继承容易出错

描述
Shield是ERC1820ImplementerInterface的一个实现,但它没有从该接口继承。这种行为容易出错,可能会阻止实现或接口正确更新。

建议
让Shield继承ERC1820ImplementerInterface。

EthKids

Crytic在EthKids中发现了另一个不寻常的问题:this.balance包含当前交易(msg.value)的金额,这可能导致值计算不正确。

问题8:Msg.value在fundWithAward中被使用了两次

描述
fundWithAward中使用this.balance没有考虑已经由msg.value添加的值。因此,价格计算不正确。

fundWithAward通过调用calculateReward(msg.value, _donor)计算代币数量:

1
2
function fundWithAward(address payable _donor) public payable onlyWhitelisted {
   uint256 _tokenAmount = calculateReward(msg.value, _donor);

calculateReward调用calculatePurchaseReturn,其中_reserveBalance是this.balance,_depositAmount是msg.value:

1
  return bondingCurveFormula.calculatePurchaseReturn(_tokenSupply, _tokenBalance, address(this).balance, _ethAmount);

在calculatePurchaseReturnn中,baseN是通过将_depositAmount(msg.value)加到_reserveAmount(this.balance)来计算的:

1
uint256 baseN = _depositAmount.add(_reserveBalance);

msg.value已经包含在this.balance中。例如,如果this.balance在交易前是10 ether,而msg.value是1 eth,那么this.balance在交易期间将是11 ether。因此,msg.value被使用了两次,calculatePurchaseReturn的计算不正确。

建议
更改价格计算,使_reserveBalance不包含交易中发送的金额。

HQ20

最后,Crytic在HQ20中发现了一个众所周知的问题:重入。如果合约与具有回调功能的代币(如ERC777)一起使用,就会发生这种重入。这与最近的uniswap和lendf.me黑客攻击类似。

问题9:重入可能导致代币被盗

描述
Classifieds在外部代币上调用transferFrom,但没有遵循检查-效果-交互模式。如果目标代币具有回调机制(如ERC777代币),这会导致攻击者可以利用的重入。

有两个方法存在重入问题:

  • Classifieds.cancelTrade(uint256)
  • Classifieds.executeTrade(uint256)

建议
遵循检查-效果-交互模式。

立即开始使用Crytic!

Crytic可以保护您的代码库免受关键漏洞的影响,并帮助您设计更安全的代码。何乐而不为呢?

立即注册Crytic。有问题吗?加入我们的Slack频道(#crytic)或在Twitter上关注@CryticCi。

如果您喜欢这篇文章,请分享: Twitter | LinkedIn | GitHub | Mastodon | Hacker News

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计