Root + Impact: // src/Snow.sol
contract Snow is ERC20 {
// @> BUG: burn() / burnFrom() me allowance check nahi hai
// @> BUG: Koi bhi kisi ke bhi Snow burn kar sakta hai
function burn(address from, uint256 amount) external {
_burn(from, amount); // no allowance, no msg.sender == from check
}
// Normal ERC20Burnable me burnFrom() allowance check karta hai,
// yahan wo check missing hai
}
Description: Description: Missing allowance check in Snow burn allows attacker to burn victim's Snow and block claims
Snow token ko claim karne ke liye chahiye. User ko Snow hold karke SnowmanAirdrop.claimSnowman() call karna hota hai.
Par Snow.sol me burn() function me ye check hi nahi hai ki burn karne wala wahi hai jiska Snow hai, ya uske paas allowance hai ya nahi.
Attacker kisi bhi victim ka address daal ke uska Snow burn kar sakta hai. Victim ka Snow 0 ho jayega, to wo claim nahi kar payega. Attacker pure airdrop ke saare users ka Snow jala ke sabka claim rok sakta hai.
Result: Ek transaction se kisi ka bhi Snow khatam, sabka claim DoS.
Risk
Likelihood:High - Attacker ko victim ki private key nahi chahiye. Bas victim ka address public hai, aur burn() public hai. Ek loop me 100 users ka Snow jala sakta hai.
Impact:High - Victims ka Snow gayab, wo NFT claim nahi kar payenge. Snow ki value aur trust dono khatam. Attacker bina paisa kharch kiye poora airdrop rok sakta hai.
Proof of Concept: High - Victims ka Snow gayab, wo NFT claim nahi kar payenge. Snow ki value aur trust dono khatam. Attacker bina paisa kharch kiye poora airdrop rok sakta hai.
pragma solidity ^0.8.24;
import {Test} from "forge-std/Test.sol";
import {Snow} from "../src/Snow.sol";
contract BurnGriefTest is Test {
Snow snow;
address victim = makeAddr("victim");
address attacker = makeAddr("attacker");
function setUp() public {
snow = new Snow();
snow.mint(victim, 100 ether);
}
function test_AttackerBurnsVictimSnow() public {
assertEq(snow.balanceOf(victim), 100 ether);
vm.prank(attacker);
snow.burn(victim, 100 ether);
assertEq(snow.balanceOf(victim), 0);
}
}
Recommended Mitigation: Mitigation: Add allowance check + only msg.sender can burn own Snow
- remove this code
+ add this code
Root Cause:
burn() me msg.sender == from ya allowance ka check missing hai.
Why this fix works:
Agar sirf owner hi apna Snow burn kar sake, ya jiske paas allowance hai wahi burn kar sake, to attacker victim ka Snow nahi jala payega. OpenZeppelin ka ERC20Burnable yahi karta hai.
Recommended Fix - Step by Step:
Step 1 - Use OpenZeppelin ERC20Burnable:
burn() sirf msg.sender ka Snow burn kare, burnFrom() allowance check kare.
Step 2 - Add allowance check in burnFrom():
Require allowance[from][msg.sender] >= amount, phir allowance kam karo, phir burn karo.
Step 3 - Remove custom burn(address, amount):
Custom burn(address) function ko hata do, sirf standard burn() aur burnFrom() rakho.
After Fix Verification:
- Victim apna Snow burn kare -> should SUCCEED
- Attacker bina allowance ke victim ka Snow burn kare -> should REVERT
- Airdrop contract allowance ke saath burn kare -> should SUCCEED
Fix Code:
// In Snow.sol - use standard OpenZeppelin
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
contract Snow is ERC20, ERC20Burnable {
// Remove custom burn(address, amount)
// burn() -> only burns msg.sender's tokens
// burnFrom(from, amount) -> checks allowance first
}