
There’s a lending pool with a million DVT tokens in balance, offering flash loans for free. If only there was a way to attack and stop the pool from offering flash loans … You start with 100 DVT tokens in balance.
UnstoppableLender.sol

By using the ReentrancyGuard contract, ReentrancyGuard’s nonReentrant Modifier is applied to the internal functions of the main contract to prevent reentrancy calls from occurring.
Two State Variables are being used. The damnValuableToken variable is assigned as an IERC20 token. The poolBalance variable is used as the flashLoan pool within the contract.
You can see that the constructor sets token address as a factor of address type.
Let’s check the function. depositTokens(uint256 amount) external nonReentrant
When proceeding to this contract based on passing the condition that the amount value must be greater than 0, by default, the amount must be transferred with a positive value.
Call the token contract function transferFrom and send to this contract in balance as much as the value of the variable
Finally, add the value of the amount sent to the poolBalance value.
Let’s check the function. flashLoan(uint256 borrowAmount) external nonReentrant
Depending on the value of the borrowAmount parameter, you can take out a loan from the balance deposited in the contract pool.
You can see various Error Handling patterns. The borrowAmount value must be greater than zero, which can be borrowed from the minimum positive value of 1. you can get a loan from the balance deposited in the contract pool.
BalanceBefore Local Variable is assigned the Current Contract Balance. Must be greater than or equal to the value of browAmount. The value you want to borrow must not exceed the Contract Balance value.
In code assert(poolBalance==balanceBefore) ErrorHandling, the value of the poolBalance variable must be the same as the value of the balanceBefore variable. This condition is because the value of the poolBalance variable was updated by depositing the balance to this contract in the depositTokens function and adding the value of the poolBalance variable to the value of the amount variable.
Send the value of the browAmount variable to msg.sender using the transfer function. (This section completes the transfer.)
IReceiver(msg.sender).receiveTokens(address(damnValuableToken), borrowAmount) Transfer the token’s address value and loan balance value to the receiver Tokens function defined in the external contract to proceed with loan repayment. After completing the loan, this Contract Current Balance compares the values of the previous Balance to complete the flashloan operation.
ReceiverUnstoppable.sol

UnstoppableLender private immutable pool;
Assign a distribution address for the Unstoppable deployed contract.
address private immutable owner;
Assigns the address of msg.sender in the current contract. Functions
constructor(address poolAddress)
Set up the distribution address of the UnstoppableLender contract and the current contract msg.sender address value in each state variable.
function receiveTokens(address tokenAddress, uint256 amount) external
We will proceed with the repayment of the loan while working on the flash loan.
Compare the msg.sender address value with the contract address value because UnstoppableLender contract only calls this function.
Finalize the loan repayment based on the transfer function.
function executeFlashLoan(uint256 amount) external
UnstoppableLender contract internal flashLoan functions can be call.
To make a call, the address value of the msg.sender address, that is, the address value of the call contract, must be the same as the address value of msg.sender when deploying the RecoverUnstoppable contract.
Only the owner should be able to run flashloan for this part.
The final attack objective of the target contract should disable the flash loan System, causing DOS. As a result of the contract code auditing, you can see that it is a simple flash Loan pattern. flashLoan(uint256 borrowAmount) external nonReentrant To disable a function, you must carefully analyze its dependencies, such as variables and external contracts.
This function uses a number of state variables to perform an Error Handling test. You can see the state variable PoolBalance. Verify that the value of this variable is the same for the balance before the loan, which is the balance Before variable. If the condition fails because of the use of assert(poolBalance==balanceBefore); assert handle, it causes an error after exhausting gas.
PoolBalance StateVariable is used to track and trigger the balance by adding the balance to the GlobalVariable value msg.sender balance during the deposition to this flashLoan pool contract via the depositionTokens function.
You can see that there is a problem with the error handling method.In the depositTokens function, the balance of the UnstoppableLender Contract is added. The balance value is stored equally in the poolBalance variable and is used in the flashLoan function to check the balance condition.
What happens if you force the transfer function to add the balance without calling the depositToken function? By bypassing the logic configured in the contract, the balance value of the contract was added abnormally, so assert(poolBlance == balanceBefore); error handling was performed on flashLoan, which could cause a contract error to cause DOS.
Call the transfer function without adding a contract balance through the depoitToken function to add a contract balance, and finally call the flashLoan function. An exception occurred successfully through testing, and DOS attack completed successfully.

I think that the vulnerability is related to [SWC-132]Unexpected EtherBalance content in the Smart Contract Weakness Classification Registry.

Thank you for the @tinchoabbate that made a good wargame.

There’s a lending pool with a million DVT tokens in balance, offering flash loans for free. If only there was a way to attack and stop the pool from offering flash loans … You start with 100 DVT tokens in balance.
UnstoppableLender.sol

By using the ReentrancyGuard contract, ReentrancyGuard’s nonReentrant Modifier is applied to the internal functions of the main contract to prevent reentrancy calls from occurring.
Two State Variables are being used. The damnValuableToken variable is assigned as an IERC20 token. The poolBalance variable is used as the flashLoan pool within the contract.
You can see that the constructor sets token address as a factor of address type.
Let’s check the function. depositTokens(uint256 amount) external nonReentrant
When proceeding to this contract based on passing the condition that the amount value must be greater than 0, by default, the amount must be transferred with a positive value.
Call the token contract function transferFrom and send to this contract in balance as much as the value of the variable
Finally, add the value of the amount sent to the poolBalance value.
Let’s check the function. flashLoan(uint256 borrowAmount) external nonReentrant
Depending on the value of the borrowAmount parameter, you can take out a loan from the balance deposited in the contract pool.
You can see various Error Handling patterns. The borrowAmount value must be greater than zero, which can be borrowed from the minimum positive value of 1. you can get a loan from the balance deposited in the contract pool.
BalanceBefore Local Variable is assigned the Current Contract Balance. Must be greater than or equal to the value of browAmount. The value you want to borrow must not exceed the Contract Balance value.
In code assert(poolBalance==balanceBefore) ErrorHandling, the value of the poolBalance variable must be the same as the value of the balanceBefore variable. This condition is because the value of the poolBalance variable was updated by depositing the balance to this contract in the depositTokens function and adding the value of the poolBalance variable to the value of the amount variable.
Send the value of the browAmount variable to msg.sender using the transfer function. (This section completes the transfer.)
IReceiver(msg.sender).receiveTokens(address(damnValuableToken), borrowAmount) Transfer the token’s address value and loan balance value to the receiver Tokens function defined in the external contract to proceed with loan repayment. After completing the loan, this Contract Current Balance compares the values of the previous Balance to complete the flashloan operation.
ReceiverUnstoppable.sol

UnstoppableLender private immutable pool;
Assign a distribution address for the Unstoppable deployed contract.
address private immutable owner;
Assigns the address of msg.sender in the current contract. Functions
constructor(address poolAddress)
Set up the distribution address of the UnstoppableLender contract and the current contract msg.sender address value in each state variable.
function receiveTokens(address tokenAddress, uint256 amount) external
We will proceed with the repayment of the loan while working on the flash loan.
Compare the msg.sender address value with the contract address value because UnstoppableLender contract only calls this function.
Finalize the loan repayment based on the transfer function.
function executeFlashLoan(uint256 amount) external
UnstoppableLender contract internal flashLoan functions can be call.
To make a call, the address value of the msg.sender address, that is, the address value of the call contract, must be the same as the address value of msg.sender when deploying the RecoverUnstoppable contract.
Only the owner should be able to run flashloan for this part.
The final attack objective of the target contract should disable the flash loan System, causing DOS. As a result of the contract code auditing, you can see that it is a simple flash Loan pattern. flashLoan(uint256 borrowAmount) external nonReentrant To disable a function, you must carefully analyze its dependencies, such as variables and external contracts.
This function uses a number of state variables to perform an Error Handling test. You can see the state variable PoolBalance. Verify that the value of this variable is the same for the balance before the loan, which is the balance Before variable. If the condition fails because of the use of assert(poolBalance==balanceBefore); assert handle, it causes an error after exhausting gas.
PoolBalance StateVariable is used to track and trigger the balance by adding the balance to the GlobalVariable value msg.sender balance during the deposition to this flashLoan pool contract via the depositionTokens function.
You can see that there is a problem with the error handling method.In the depositTokens function, the balance of the UnstoppableLender Contract is added. The balance value is stored equally in the poolBalance variable and is used in the flashLoan function to check the balance condition.
What happens if you force the transfer function to add the balance without calling the depositToken function? By bypassing the logic configured in the contract, the balance value of the contract was added abnormally, so assert(poolBlance == balanceBefore); error handling was performed on flashLoan, which could cause a contract error to cause DOS.
Call the transfer function without adding a contract balance through the depoitToken function to add a contract balance, and finally call the flashLoan function. An exception occurred successfully through testing, and DOS attack completed successfully.

I think that the vulnerability is related to [SWC-132]Unexpected EtherBalance content in the Smart Contract Weakness Classification Registry.

Thank you for the @tinchoabbate that made a good wargame.
<100 subscribers
<100 subscribers
Share Dialog
Share Dialog
No comments yet