DeFiVulnLabs靶场全系列详解(四十六)闪电贷缺少发起人检查

admin 2025年6月6日09:14:10评论6 views字数 4860阅读16分12秒阅读模式
01
前言
此内容仅作为展示Solidity常见错误的概念证明。它严格用于教育目的,不应被解释为鼓励或认可任何形式的非法活动或实际的黑客攻击企图。所提供的信息仅供参考和学习,基于此内容采取的任何行动均由个人全权负责。使用这些信息应遵守适用的法律、法规和道德标准。
DeFiVulnLabs一共有47个漏洞实验,包括各种经典的合约漏洞和一些少见的可能造成安全问题的不安全代码,本系列将逐一解析每个漏洞,包括官方的解释和自己的理解。
02
闪电贷缺少发起人校验

漏洞解析:

    闪电贷虽然是可以对任何人开放使用的,因为其区块链的特性能保证借款人一定能还上钱,因此任何人都可以发起闪电贷交易。那么为什么还要校验发起人呢?
    主要是为了防止滥用,例如某些平台只希望经过平台合法验证后的用户来进行闪电贷,包括一些合规方面以及防止一些意外的安全行为
代码地址:
https://github.com/SunWeb3Sec/DeFiVulnLabs/blob/main/src/test/Flashloan-flaw.sol

代码解析:

    代码定义了一个借贷池和一个银行系统,借贷池合约主要有一个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 datapublic{//在闪电贷操作开始之前,记录合约的代币余额:        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");    }}
   LendingPool合约中的flashload会进行转账给调用者的操作,然后在flashload中调用了executeOperation,这个方法主要的作用就是将之前借的代币全部归还给借贷池。
   function executeOperation(        uint256 amounts,        address receiverAddress,        address _initiator,        bytes calldata data) external {        //修复代码,就是添加这一行        //  require(_initiator == address(this), "Unauthorized");        //归还所有代币给借贷池        IERC20(USDa).safeTransfer(address(lendingPool), amounts);    }}
安全问题:simpleBank合约存在executeOperation函数
接下来主要关注的是simpleBank里的flashloan和executeOperation,flashloan主要是引用的借贷池的flashloan,然后会自动调用simpleBank里的executeOperation进行还款。
executeOperation的四个参数

            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 dataexternal {          //外部传参没有用        receiverAddress = address(this);  //接受地址为当前合约        //借款金额,接受地址,额外数据        lendingPool.flashLoan(amounts, receiverAddress, data);      }    //借款人的 executeOperation 函数    function executeOperation(        uint256 amounts,        address receiverAddress,        address _initiator,        bytes calldata dataexternal {        /* 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);    }}
03
复现
以下是复现代码
首先是实例化了LendingPool合约和SimpleBank,然后调用,可以看到调用的地址此时是0x0,但是testFlashLoanFlaw还是成功了
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 {}}

如果没有存在校验的话那么任何人都可以进行调用这个函数,造成预期外的风险。虽然我目前还没发现有什么更严重的安全风险和问题,但是这种缺少发起人的检查却是实打实的一种“风险控制类”的缺失。

DeFiVulnLabs靶场全系列详解(四十六)闪电贷缺少发起人检查
可以看到校验了调用者必须是simpleBank合约本身,所以就直接失败了(revert
DeFiVulnLabs靶场全系列详解(四十六)闪电贷缺少发起人检查
04
如何修复该问题

上面所谓的修复,就是添加了这一行代码  

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合约本身,而不是能是其他的合约

05
感谢关注
个人语雀账号:https://www.yuque.com/iceqaq

原文始发于微信公众号(Ice ThirdSpace):DeFiVulnLabs靶场全系列详解(四十六)闪电贷缺少发起人检查

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月6日09:14:10
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   DeFiVulnLabs靶场全系列详解(四十六)闪电贷缺少发起人检查http://cn-sec.com/archives/4138892.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息