漏洞解析:
代码解析:
代码定义了一个借贷池和一个银行系统,借贷池合约主要有一个flashLoan方法,这个方法主要实现了:
-
1、检查合约是否有足够的流动性。
-
2、将代币转移给借款人。
-
3、调用借款人的
executeOperation
函数。 -
4、检查借款人在操作后是否归还了代币。
//实现了闪电贷的主要功能
contract LendingPool {
IERC20 public USDa;
//设置USDA为交易代币
constructor(address _USDA) {
USDa = IERC20(_USDA);
}
//提供闪电贷功能,将代币转移给借款人
functionflashLoan(
uint256 amount,
address borrower,
bytes calldata data
) public{
//在闪电贷操作开始之前,记录合约的代币余额:
uint256 balanceBefore = USDa.balanceOf(address(this));
//确保余额大于借款金额
require(balanceBefore >= amount, "Not enough liquidity");
//给借款人打币
require(USDa.transfer(borrower, amount), "Flashloan transfer failed");
//调用借款人的 executeOperation 函数
IFlashLoanReceiver(borrower).executeOperation(
amount,
borrower,
msg.sender,
data
);
//查询借款后合约的地址上的USAD代币余额
uint256 balanceAfter = USDa.balanceOf(address(this));
//目的是确保在闪电贷操作完成后,合约的代币余额至少等于贷款前的余额
//确保贷款在同一个区块内归还
require(balanceAfter >= balanceBefore, "Flashloan not repaid");
}
}
function executeOperation(
uint256 amounts,
address receiverAddress,
address _initiator,
bytes calldata data
) external
{
//修复代码,就是添加这一行
// require(_initiator == address(this), "Unauthorized");
//归还所有代币给借贷池
IERC20(USDa).safeTransfer(address(lendingPool), amounts);
}
}
1、amounts 借的资金的数量(归还的资金的数量)
2、receiverAddress 表示接受资金的地址
3、_initiator 表示发起闪电贷的地址
4、calldata 表示额外传递的数据
executeOperation
函数没有任何权限检查,任何地址都可以调用这个函数,并归还代币给LendingPool
。//简单的银行
contract SimpleBankBug {
using SafeERC20 for IERC20; //实例化ERC20
IERC20 public USDa;
LendingPool public lendingPool; //实例化借贷池,也就是实例化上面的LendingPool合约
//初始化合约,传入借贷池合约和Token合约
constructor(address _lendingPoolAddress, address _asset) {
lendingPool = LendingPool(_lendingPoolAddress);
USDa = IERC20(_asset);
}
//闪电贷合约
function flashLoan(
uint256 amounts,
address receiverAddress,
bytes calldata data
) external
{
//外部传参没有用
receiverAddress = address(this); //接受地址为当前合约
//借款金额,接受地址,额外数据
lendingPool.flashLoan(amounts, receiverAddress, data);
}
//借款人的 executeOperation 函数
function executeOperation(
uint256 amounts,
address receiverAddress,
address _initiator,
bytes calldata data
) external
{
/* Perform your desired logic here
Open opsition, close opsition, drain funds, etc.
_closetrade(...) or _opentrade(...)
*/
//修复代码,就是添加这一行
// require(_initiator == address(this), "Unauthorized");
//归还所有代币给借贷池
IERC20(USDa).safeTransfer(address(lendingPool), amounts);
}
}
contract ContractTest is Test {
USDa USDaContract;
LendingPool LendingPoolContract;
SimpleBankBug SimpleBankBugContract;
FixedSimpleBank FixedSimpleBankContract;
function setUp() public {
USDaContract = new USDa();
LendingPoolContract = new LendingPool(address(USDaContract));
SimpleBankBugContract = new SimpleBankBug(
address(LendingPoolContract),
address(USDaContract)
);
USDaContract.transfer(address(LendingPoolContract), 10000 ether);
FixedSimpleBankContract = new FixedSimpleBank(
address(LendingPoolContract),
address(USDaContract)
);
}
function testFlashLoanFlaw() public {
LendingPoolContract.flashLoan(
500 ether,
address(SimpleBankBugContract),
"0x0"
);
}
function testFlashLoanSecure() public {
LendingPoolContract.flashLoan(
500 ether,
address(FixedSimpleBankContract),
"0x0"
);
}
receive() external payable {}
}
如果没有存在校验的话那么任何人都可以进行调用这个函数,造成预期外的风险。虽然我目前还没发现有什么更严重的安全风险和问题,但是这种缺少发起人的检查却是实打实的一种“风险控制类”的缺失。
上面所谓的修复,就是添加了这一行代码
require(_initiator == address(this), "Unauthorized");
function executeOperation(
uint256 amounts,
address receiverAddress,
address _initiator,
bytes calldata data
) external
{
//修复代码,就是添加这一行
require(_initiator == address(this), "Unauthorized");
//归还所有代币给借贷池
IERC20(USDa).safeTransfer(address(lendingPool), amounts);
}
}
校验了executeOperation函数接受的参数为(amounts 金额、receiverAddress 接受地址,发起人地址,附加数据)
_initiator 是调用的flashLoan时的msg.sender 也就是发起人==address(this),这就意味着发起人只能是SimpleBankBug
合约本身,而不是能是其他的合约
原文始发于微信公众号(Ice ThirdSpace):DeFiVulnLabs靶场全系列详解(四十六)闪电贷缺少发起人检查
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论