智能合约漏洞篇:常见漏洞介绍及整数溢出漏洞分析

一、常见漏洞介绍:

智能合约是运行在区块链上的自动执行代码。由于它们的不可逆性和与金钱相关的特性,智能合约的安全问题受到了广泛关注。以下是一些常见的智能合约漏洞:

  1. 整数溢出与下溢:当整数变量的值超出其允许的范围时,可能会发生溢出或下溢,导致不可预测的结果。

  2. 重入攻击:在一个函数调用中,被调用的合约再次调用原函数,可能导致不希望的多次执行。例如,著名的DAO攻击就是重入攻击的一种。

  3. 短时间浮动:在区块链上,时间和块高度可以被操纵。攻击者可以利用这点,通过挖矿操作来操纵合约的时间逻辑。

  4. 随机性问题:在以太坊等区块链平台上,产生随机数是有挑战的,因为所有信息都是公开的。攻击者可能预测或操纵随机结果。

  5. 委托调用:使用delegatecall或其他低级调用可能导致合约中的状态被意外修改。

  6. 异常处理不当:如果合约在遇到异常时没有正确处理,可能导致资金丢失或被锁定。

  7. 未初始化的存储指针:如果合约使用了未初始化的存储指针,可能会导致存储被意外修改。

  8. 控制流可预测性:如果攻击者可以预测或操纵合约的控制流,他们可能会利用这点执行恶意操作。

  9. 前端/后端不匹配:智能合约的后端代码和前端应用的逻辑可能存在不匹配,导致用户资金损失。

  10. 权限过于宽松:如果合约的权限控制设置得过于宽松,攻击者可能利用这点执行不应允许的操作。

  11. 固定合约逻辑:由于智能合约一旦部署就无法更改,任何发现的漏洞都无法直接修复。需要有升级机制来处理此问题。

二、整数溢出漏洞分析:

1.整数溢出类型:

  • 乘法溢出
  • 加法溢出
  • 减法溢出
  • uint8到uint256,以及int8到int256

2..整数溢出原理分析:

溢出举例:
Example 1:
function withdraw(uint _amout) {
	require(balances[msg.sender] - _amout > 0);
	msg.sender.transfer(_amout);
	balances[msg.sender] -= _amout;
}

balances[msg.sender]_amout都是无符号的int型本身不存在小于0的情况,这里的两个值只要不相等,判断就会成立,就可以通过第四行达到一个减法下溢。

Example 2:
function popArrayOfThings() {
	require(arrayOfThings.length >= 0);
	arrayOfThings.length--;
}

当数组arrayOfThings长度为0时,同样会造成一个减法下溢,从而导致可以以此来控制其他地方的变量。

Example 3:
for (var i = 0; i < somethingLarge; i++){
	// ...
}

var是一个uint8类型,假设somethingLarge是一个uint256类型的值,那么这里的i永远不会超过256.

防止措施:

加法溢出:

function safeAdd(uint256 a,uint256 b) internal
returns (uint256){
	uint256 c = a + b;
	assert(c>=a && c>=b);
	return c;
}

减法溢出:

function safeSub(uint256 a,uint256 b) internal
returns (uint256){
	assert(b <= a);
	return a - b;
}

乘法溢出:

function safeMul(uint256 a,uint256 b) internal
returns (uint256){
	uint256 c = a * b;
	assert(a == 0 || c / a == b);
	return c;
}

 3.整出溢出漏洞实例分析:

将题目部署到测试链上面后,审一下代码:

pragma solidity >=0.4.22 <0.6.0;

contract Vul {              
mapping (address => uint) balances;     //定义了一个键值对,变量名为balances
                                      // 一个钱包地址对应一个账户余额

event sendflag(string b64email); 

function payforflag(string memory b64email) public{  //花钱购买flag
        require(balances[msg.sender] >= 10000000000);
        balances[msg.sender]=0;
        emit sendflag(b64email);
    }

function balanceOf() view public returns (uint)  {    //返回当前账户的余额
    return balances[msg.sender];
}

function deposit() public payable {         //当前账户余额加一
    balances[msg.sender] += 1;
}

function withdraw(uint _amount) public {    //从当前账户取钱(_amount变量表示取多少)
    require(balances[msg.sender] - _amount > 0);  //require用于确认条件的有效性
    balances[msg.sender] -= _amount;
}
    
}

分析代码可以知道。flag需要用币买下来,但是我们要花10000000000及以上的币才能买到flag,如果一个币一个币的申请将会十分耗时,不知道要猴年马月才能买下flag。仔细观察withdraw函数是存在一个整数下溢的漏洞的,变量_amountbalances[msg.sender]都是uint256类型,所以不存在负数,就没有小于0这一说,当我们的余额小于要取出的余额就会导致最后我们的余额超级翻倍,这里可以先获得一个币,再取两个币。这样根据uint类型的一个回还,最终我们的账户就有了2^256-1个币。

操作一下通过bavanceOf函数查看余额:

正好是2^256-1,此时就可以购买flag了

猜你喜欢

转载自blog.csdn.net/weixin_46175201/article/details/132928184
今日推荐