区块链安全学习笔记

参考:

https://www.anquanke.com/post/id/145458以太坊智能合约安全入门

https://xiaozhuanlan.com/topic/7921803456区块链技术

一、

Fallback函数:

官方解释:A contract can have exactly oneunnamed function. This function cannot have arguments and cannot returnanything. It is executed on a call to the contract if none of the otherfunctions match the given function identifier (or if no data was supplied atall).(一个合约有且只能有一个未命名的函数,这个函数不能有参数且不能返回任何值,当其他的函数不能和被给的函数匹配后者没有任何数据被提供时,此函数执行)

 

执行条件:

(1)    当该合约被转入ether时

(2)    当其他外部账户或者内部账户调用该合约一个不存在的函数时

 

目前已知的区块链的安全问题大多数和fallback函数有关

 

二、

转账函数:

.transfer()

调用错误时会throw,回滚状态

只能传递2300gas,防止重入

.send()

调用错误时会返回false布尔值

只能传递2300gas,防止重入

.gas().value().send()

调用错误时会返回false布尔值

消耗所有由gas()制定的gas,不能有效防止重入

 

开发者需要根据不同的场景合理使用这些函数来实现转币功能,如果考虑不周或者不完整,极有可能造成漏洞被攻击者利用

 

 

Call()  没有gas的限制,会将合约的所有剩余gas用以调用

callcode() 以后可能会被弃用

delegatecall() 与call()的不同是 delegatecall()仅仅是调用代码,但是处理的还是调用者合约的数据,所以要保证两个合约的存储变量能兼容,

这些call调用都是很低层的调用,当调用一个恶意的合约时,他可能会回调你的合约,所以要准备在调用返回时应对状态变量被篡改的情况。

 

三、下面列出一些常见的solidity的漏洞类型

 

1、重入漏洞 reentrancy

以太坊的智能合约漏洞其实和自身的代码逻辑有很大的关系

pragma solidity ^0.4.10;

 

contract IDMoney {

    address owner;

    mapping (address=> uint256) balances;  // 记录每个打币者存入的资产情况

 

    eventwithdrawLog(address, uint256);

   

    functionIDMoney() { owner = msg.sender; }

    functiondeposit() payable { balances[msg.sender] += msg.value; }

    functionwithdraw(address to, uint256 amount) {

       require(balances[msg.sender] > amount);

       require(this.balance > amount);

 

       withdrawLog(to, amount);  // 打印日志,方便观察 reentrancy

       

        to.call.value(amount)();  // 使用 call.value()() 进行 ether 转币时,默认会发所有的 Gas 给外部

       balances[msg.sender] -= amount;

    }

    functionbalanceOf() returns (uint256) { return balances[msg.sender]; }

    functionbalanceOf(address addr) returns (uint256) { return balances[addr]; }

}

 

出现重入漏洞的原因是call.value()会将全部剩余的gas用于外部调用,而且账户余额在提取之后才会减掉,所以造成调用的外部合约可以多次回调IDMoney合约也就是重入,直到所有的ether都被取走,所以有gas限制的send和transfer函数可以防止重入。

 

 

 

2.访问控制

Call()  没有gas的限制,会将合约的所有剩余gas用以调用,相当于普通语言中的函数调用调用完就回到原来的控制流中

 

callcode() 以后可能会被弃用

delegatecall() 与call()的不同是 delegatecall()仅仅是调用代码,但是处理的还是调用者合约的数据,所以要保证两个合约的存储变量能兼容,相当于把外部合约的代码粘贴复制过来处理本合约的数据,就是插入一段代码

这些call调用都是很低层的调用,当调用一个恶意的合约时,他可能会回调你的合约,所以要准备在调用返回时应对状态变量被篡改的情况。

 

pragma solidity ^0.4.10;

 

contract Delegate {

    address publicowner;

 

    functionDelegate(address _owner) {

        owner =_owner;

    }

    function pwn(){

        owner =msg.sender;

    }

}

 

contract Delegation {

    address publicowner;

    Delegatedelegate;

 

    functionDelegation(address _delegateAddress) {

        delegate =Delegate(_delegateAddress);

        owner =msg.sender;

    }

    function () {

        if (delegate.delegatecall(msg.data)) {

            this;

        }

    }

}

 

我理解的这个delegatecall的危害在于可以插入一部分代码到原合约的逻辑中,相当于插入一段代码,如果delegatecall的调用地址可以通过某种手段控制,那么可以插入任何代码(个人见解)

 

 

算术问题

 

整数溢出漏洞,在于利用整数溢出时在非法的输入下产生一个符合验证条件的情况,从而绕过验证

pragma solidity ^0.4.10;

 

contract MyToken {

    mapping(address => uint) balances;

 

    functionbalanceOf(address _user) returns (uint) { return balances[_user]; }

    functiondeposit() payable { balances[msg.sender] += msg.value; }

    functionwithdraw(uint _amount) {

        require(balances[msg.sender] - _amount >0);  // 存在整数溢出

       msg.sender.transfer(_amount);

       balances[msg.sender] -= _amount;

    }

}

为了防止整数溢出的发生,一方面可以在算术逻辑前后进行验证,另一方面可以直接使用OpenZeppelin 维护的一套智能合约函数库中的 SafeMath 来处理算术逻辑

 

 

未验证底层调用的返回值

未严格判断不安全函数调用返回值,这类型的漏洞其实很好理解

 

 

 

拒绝服务

不可恢复的恶意操作或者可控制的无限资源消耗,可能导致 Ether 和 Gas 的大量消耗,更严重的是让原本的合约代码逻辑无法正常运行,不在区块链上的dos攻击就是发送大量的包,比较常见的是大量的ping的icmp包,堵塞网站的带宽,但是下面的例子有点不一样

pragma solidity ^0.4.10;

 

contract PresidentOfCountry {

    address publicpresident;

    uint256 price;

 

    functionPresidentOfCountry(uint256 _price) {

       require(_price > 0);

        price =_price;

        president =msg.sender;

    }

 

    functionbecomePresident() payable {

       require(msg.value >= price); // must pay the price to becomepresident

        president.transfer(price);   // we pay the previous president

        president =msg.sender;      // we crown the newpresident

        price =price * 2;           // we double theprice to become president

    }

}

 

攻击代码

contract Attack {

    function () {revert(); }

 

    functionAttack(address _target) payable {

       _target.call.value(msg.value)(bytes4(keccak256("becomePresident()")));

    }

}

 

感觉和dos的关系并不是很大,利用的是transfer回调fallback函数时在fallback函数中故意引起错误,导致原函数不能调用错误发生状态回滚,不能继续执行

 

 

Bad randomness 不可预测的随机处理

伪随机问题一直都存在于现代计算机系统中,但是在开放的区块链中,像在以太坊智能合约中编写的基于随机数的处理逻辑感觉就有点不切实际了,由于人人都能访问链上数据,合约中的存储数据都能在链上查询分析得到。如果合约代码没有严格考虑到链上数据公开的问题去使用随机数,可能会被攻击者恶意利用来进行 “作弊”。

 

在区块链上获取一个随机数是很难的,因为很大一部分是伪随机数,所以随机数产生的逻辑区块链上的智能合约和状态都是可以查询的,即使这个状态变量是私有的,也可以通过查询相关的交易来获得私有变量的值,

 

获取随机数的时候不要用一些block.hash block.stamp等变量 block.hash只能访问最近的256个块,且在当前块中获得当前的块hash总是为零,我理解的是当前块的hash已经确定时,那么这个块的数据也就确定了,无法更改通过块hash获取到的值,先有鸡还是先有蛋的问题,所以规定当前区块的hash为零(个人理解), block.stamp是矿工可以随意控制的变量,所以作为随机数的来源也不合适

 

一切链上的数据都是公开的,想要获取一个靠谱的随机数,使用链上的数据看来是比较难做到的了,这里有一个独立的项目 Oraclize 被设计来让 Smart Contract 与互联网进行交互,有兴趣的同学可以深入了解一下。(附上基于 Oraclize 的随机数获取方法 randomExample)

 

提前交易

 

在以太坊中所有的 TX 都需要经过确认才能完全记录到链上,而每一笔 TX 都需要带有相关手续费,而手续费的多少也决定了该笔 TX 被矿工确认的优先级,手续费高的 TX 会被优先得到确认,而每一笔待确认的 TX 在广播到网络之后就可以查看具体的交易详情,一些涉及到合约调用的详细方法和参数可以被直接获取到,所以攻击者可以用更高的价格发起一个交易,在被攻击者的交易得到确认之前上链,

 

时间篡改

说白了一切与时间相关的漏洞都可以归为 “Time Manipulation”。在 Solidity 中,block.timestamp (别名 now)是受到矿工确认控制的,也就是说一些合约依赖于block.timestamp 是有被攻击利用的风险的,当攻击者有机会作为矿工对 TX 进行确认时,由于block.timestamp 可以控制,一些依赖于此的合约代码即预知结果,攻击者可以选择一个合适的值来到达目的

 

短地址攻击

Call调用时将每个参数按照abi协议打包为32字节,所以一个call调用的msg.data的格式应该为

4字节的调用函数的函数签名+32字节格式的参数1+32字节格式的参数2+……

 

当有个参数是地址类型时,且攻击者在传参的时候找到一个地址末尾为几个零的地址,比如两个零,传参的时候去掉这几个零msg.data变为

4字节的调用函数的函数签名+31字节格式的地址+32字节格式的参数2+……

(地址变短了)

 

 

Evm虚拟机在解析地址时会在每个参数的后边补两个零,所以造成其他参数的值也发生了变化,这就是短地址攻击


你可能感兴趣的:(blockchain)