智能合约安全常见攻击与防御
Web2 安全和智能合约安全区别
Web2 | 智能合约 |
---|---|
核心代码非公开 | 代码公开 |
信息 | 价值 |
身份非隐匿性 | 身份隐匿性 |
代码可修改 | 代码不易修改 |
payable函数一定会付款
如果合约内调用了没有的函数就进入fallback函数
call某个合约
重入
每次depositFunds函数,进入钱后,累加
每次withdrawFunds函数,每次取钱间隔至少1周
攻击者合约
每次调用pwbEtherStore函数必须转入一笔钱,钱存入stherStore。depositFunds.Value,紧接着马上使用etherStore.withdrawFunds将这笔钱取出来,看上去没有攻击
forback函数(空函数)判定是否>1th,否则不取
但是在执行取钱函数后会调用forback函数再取一笔钱,就会再次执行取钱函数withdrawFunds,这样形成无限循环直到钱取空。
解决方法
1. 加修饰,禁止重入修饰器
2. 16行与17行互换,先计算再扣款转出钱
- 如果仅仅是调用16行与17行,还有个就是初次用户可能会发生小额多次取款记录的问题,也是重入问题
ANT攻击
算术溢出
规定最多给20人转账
判断要转的钱是否小于钱包
在循环转给每一个人一笔钱
问题
如果我要个两个人转钱,把value设置成非常大的数字,比如2的256次方减1 * 2
蜜罐合约
低端局
让你知道是有问题的bug合约,然后被开发者利用
代码为如果大于1th,就把所有资产转出
一直往后拖,代码里藏着一行代码
开源的合约可能存在蜜罐合约
高端局
如果你转101个eth大于自身的100个eth,那么就会将所有201个eth转给你
合约流程逻辑
1. 验证签名
2. 检查交易owner,交易+1
3. 如果涉及eth转账,再交易之前,先将客户钱转到contract(合同),所以balance已经加过msg.value,那么你永远不可能大于balace,判断语句永远不会执行。
结构体局部变量引起的变量覆盖
意外之财
和蜜罐一直,this.balance已经加过msg.value,永远达不成
Delegatecall(委托调用)攻击
1. 右边先调用setStart函数,但是左边没有该函数则进入forback函数
2. forback函数会调用setStart函数,修改当前合约Slot0的值
3. 黑客会发送参数attcaker的合约地址到Slot0
4. 任何人调用withdraw函数,因为当前合约地址为攻击者地址,执行第12行代码时,但是攻击者没有setFibonacci这个方法,那么会进入攻击者forback函数的逻辑执行
5. 将所有余额转到attacker地址,现在this.balance的值就是FibonacciBalance合约的值
6. calculatedFibNumber被右边函数设置为了0,所以左边第13行代码也会执行,转0钱,所以函数不会执行失败。如果函数失败攻击会执行不成功。
默认可见性
如果internal函数没有被明确表示,默认为public,被外部掉用内部函数产生的安全风险
随机错觉
1. 左边代码算一值,如果为真钱转走,看上去seed值计算很复杂很难预测
2. 可以计算出符合的timestamp值
3. public可被外部调用,攻击者将msg.sender构造成了合约,让合约原子化的调用这个方法,可以无损计算值
外部合约引用
左边用老的合约地址,右边每次新建一个合约地址
外部合约意外销毁,你的合约也就废了不能运行了,导致你的eth卡在那个合约里面了
短地址/参数攻击
地址末尾是0,类似长度不够交易所自动补0,导致金额损失
还是参数长度判定没有做好
未检查的返回值
没有判断winner.send返回值,不知道是否发送成功导致的安全问题
竞争条件/预先交易
碰撞Hash很难,solve函数代码表示如果你提供了一个与hash相同的值,那么将转走1000个eth
除了原作者,看上去很安全。
如果有黑客一直监控交易池,知道要用了FindThisHash合约solve方法,他会立刻重新构造一笔一摸一样的交易,他会把gas fish提的非常非常高,这样矿工就会优先打包他的交易,而不是原作者合约交易,导致即使他不知道solve solution是什么,但他也可以拿到这个钱。
拒绝服务
外部可以调用invest函数,push一个sender,distribute则便利数组investors。
当investors长度过长空间不够一个区块计算完distribute这个函数,那么这个函数永远无法正确的被纳入到某个区块当中
如果invest是个合约,给他赚钱触发了forback方法,如果他在forback方法里写了 revert, 导致你给他转钱转不成功,间接导致这一批人交易都被 revert 掉。
invest有可能是UA地址,也有可能是合约地址
时间戳操作
我们认为now是随机数,但是矿工是可以操作这个随机数的。
未初始化的存储指针
变量unlocked一定要初始化,如果没有初始化,最后的require(unlocked)会导致失败,执行自己的空默认值。
tx.origin 身份判断
左边判定 tx.origin 是否等于 owner,就把当前合约的钱转出这个人
社工引诱 owner 给当前的 attacker 赚钱,比如我向你借0.01个eth,普通就打了0.01个eth,如果打出的地址是attacker的合约地址,那么就会进入attacker 的 forback 函数逻辑,那么会调用 withdrawAll函数,那么 tx.origin 等于 owner,攻击发生。
重入攻击经典案例,先改变状态再转账。
总结
deletgatecall与合约存储相关错误是重灾区