加法溢出 乘法溢出 减法溢出
解决方法:采用openzepplin的SafeMath的library
案例一BEC:在乘法时没有使用Safemath
重入攻击可以简单理解为递归,在以太坊智能合约中,调用外部合约或Ether发送到地址,这些外部调用可能被攻击者劫持。
(1)几种转币方式:
transfer()转账失败返回之前状态,只能传送2300Gas和send()转账失败返回false退回之前状态,只能传送2300Gas
gas().call.value():发送失败返回false,传递所有可用gas供调用(可通过gas(gas_value)限制),不能防止重入
(2)fallback函数
合约中可以有唯一的未命名函数,该函数不能有实参,不能返回任何值。
执行时机
当外部账户或其他合约向该合约地址发送Ether时,fallback函数会被调用
当外部账户或其他合约向该合约地址发送Ether,但是内部没有fallback函数时,会抛出异常,然后将以太币退回给发送方
当外部账户或其他合约调用了一个该合约中不存在的函数时,fallback函数会被调用
(3)payable标识的函数
函数上增加payable,可接受Ether,把ether存在合约中
案例:
用户转账采用call函数:
add.call.value(amount)(); balances[add] -= amount;
如果进行的目标地址是合约地址,默认会调用该合约fallback函数。
重入漏洞成立条件:
合约带有足够的gas
有转账功能
状态变量在重入函数调用之后,
由代码中可以看到,在转账发生之后修改状态变量,从而导致重入攻击。
合约编写原则:
1.明确transfer、send和call的区别,理解Gas机制
2.将Ether发送给外部地址时使用Solidity内置的transfer()函数转账时只发送2300Gas,不足以调用另一个合约
3.确保状态转换发生在Ether被发送以及任何外部调用之前“检查-生效-交互”
4.添加一个在代码执行过程中所动合约的状态变量,防止重入调用(常用)
5.防止调用栈攻击,判断调用外部合约结果
6.去掉循环处理,让合约调用者控制循环
由于在ERC20的transfer函数中仅仅通过判断交易事务中是否抛出了异常,来判断该交易的状态值status。攻击者可以通过这个弱点加以攻击:例如当用户向DEX发起充值,如果交易所仅仅判断返回值,就以为充币成功,就存在假充值漏洞。
在转账代码中,尽量摒弃if-else的判断对,而是使用require来判断相应的代币结果。
安全建议:
除了判断txreceipt status之外,还应判断充值的以太坊数量是否准确增加
攻击者将自己的合约伪装成无代码的私人地址,使得受害者转账后产生损失。tx.orign是Solidity的一个全局变量,它遍历整个调用栈并返回最初发送调用(或事务)的帐户的地址。在智能合约中使用此变量进行身份验证会使合约容易受到类似网络钓鱼的攻击。在对合约调用过程无法控制情况下不要使用tx.orign
关于call()、delegatecall()、callcode()
call:最常用的调用方式,call的外部调用上下文时被调用者合约,执行环境为被调用者的运行环境,调用后内置变量msg的值会修改为调用者
delegatecall:外部调用上下文是调用者合约,执行环境为调用者的运行环境,调用后内置变量msg的值不会修改为调用者,当A delegatecall B 相当于在A的上下文中执行B的方法
callcode:call的外部调用上下文是调用者合约,环境为调用者的运行环境,调用后内置变量msg的值会修改为调用者
具体的分别:https://www.cnblogs.com/wanghui-garcia/p/9619350.html
Call方法注入漏洞外界可以直接控制合约中的call方法调用的参数,按照注入位置可以分为以下三个场景:
• 参数列表可控
•
.call(bytes4 selection, arg1, arg2, ...)• 方法选择器可控
•
.call(bytes4selection, arg1, arg2, ...)• Bytes可控
•
.call(bytesdata)•
.call(msg.data)
安全建议
delegatecall建议
进行调用时发送的data或调用的合约地址可控,复杂上下文关系中调用storage变量时可能造成变量覆盖,此种情况避免使用delegatecall进行调用,使用library进行代码复用,目前是更加优势的选择
call建议
作为开发者来说,需要对ERC223的实现进行排查,不要引入call注入问题,如果非要执行回调,则可以指定方法选择器字符串,避免使用直接注入bytes的形式来进行call调用。对于一些敏感操作或者权限判断函数,则不要轻易将合约自身的账户地址作为可信的地址。
相关的两笔交易顺序依赖,由于后一笔交易的gasprice更高而优先打包,导致安全问题的发生。特别是在tranfer中在approve之前采用一个gas费更高的提币代码就会导致在授权之前取走原本授权的以太币。
案例:
在一个抽奖的系统里,例如攻击者监控大家提交的答案,在答案正确的情况下,自己提交一个gas费更大的正确结果,使得自己的答案更快打包,获得奖励。
安全建议:
1.approve()合约所有者在修改授权代币时,先把授权值改为0,再把授权值改为需要设置的值。
require((_Value == 0)||(allowance[msg.sender][_spender]==0)) allowance[msg.sender][_spender] = _value
2.采用decreaseApprove和increaseApprove
在使用send()过程中,如果没有对send()进行返回判定,那么在外部调用合约中的fallback函数返回错误时导致返回值为false。但是并不中断交易的过程。导致转账失败。
安全建议:
1.采用withdrawal模式,每个用户都需要调用一个独立的函数来处理从合约中发送以太币的问题,独立处理发送交易失败的结果。
2.在外部转账过程中应该尽量使用transfer()函数,避免使用send函数,如果需要必须检测返回值。
攻击者通过消耗合约资源,让用户短暂的退出不可操作的合约,把以太币锁在被攻击的合约中
攻击方式出现在合约所有者希望在其投资者之间分配代币及在合约中可以看到distribute的场景。
1.当在合约中使用数组时,合约被人循环遍历增加数组的大小,让for循环执行的大小超出区块的gas费,导致distribute函数无法执行
2.另一种常见模式是所有者在合约中具有特定权限,并且必须执行一些任务才能使合约进入下一个状态。例如,ICO 合约要求所有者 finalize() 签订合约,然后才可以转让代币
3.合约被编写成为了进入新的状态需要将 Ether 发送到某个地址,或者等待来自外部来源的某些输入。这些模式也可能导致 DOS 攻击:当外部调用失败时,或由于外部原因而被阻止时。在发送 Ether 的例子中,用户可以创建一个不接受 Ether 的合约。如果合约需要将 Ether 发送到这个地址才能进入新的状态,那么合约将永远不会达到新的状态,因为 Ether 永远不会被发送到合约
解决1:在第一个例子中,合约不应该遍历可以被外部用户人为操纵的数据结构。建议使用 withdrawal 模式,每个投资者都会调用取出函数独立取出代币。
解决2:
改变合约的状态需要权限用户参与。在这样的例子中(只要有可能),如果 owner
已经瘫痪,可以使用自动防故障模式。一种解决方案是将 owner
设为一个多签名合约。另一种解决方案是使用一个时间锁,其中 [13]行 上的需求可以包括在基于时间的机制中,例如 require(msg.sender == owner || now > unlockTime)
,那么在由 unlockTime
指定的一段时间后,任何用户都可以调用函数,完成合约。这种缓解技术也可以在第三个例子中使用。如果需要进行外部调用才能进入新状态,请考虑其可能的失败情况;并添加基于时间的状态进度,防止所需外部调用迟迟不到来。
区块时间戳历来被用于各种应用,例如随机数的函数、锁定一段时间的资金、以及各种基于时间变更状态的条件语句。矿工有能力稍微调整时间戳,如果在智能合约中错误地使用区块时间戳,可以证明这是相当危险的。
矿工可以根据自己的意愿调整时间戳。在这种特殊情况下,如果合约中有足够的 Ether,挖出某个区块的矿工将被激励选择一个 block.timestamp
或 now
对 15 取余为 0
的时间戳。
区块时间戳不应该用于熵源或产生随机数——也就是说,它们不应该是游戏判定胜负或改变重要状态(如果假定为随机)的决定性因素(无论是直接还是通过某些推导)。
时效性强的逻辑有时是必需的;即解锁合约(时间锁定),几周后完成 ICO 或到期强制执行。有时建议使用 block.number
和平均区块时间来估计时间;即,10 秒的区块时间运行 1 周,约等于,60480 个区块。因此,指定区块编号来更改合约状态可能更安全,因为矿工无法轻松操纵区块编号。
如果合约不是特别关心矿工对区块时间戳的操纵,这可能是不必要的,但是在开发合约时应该注意这一点。
Solgraph
mythril
SECURIFY