code4rena-2024-03-dittoeth-h02

[H-02] An attacker can cancel other people’s short orders

보고서

Summary

데이터를 삭제하지 않고, 기존 데이터가 여전히 유효한지 확인하지 않아 다른 유저의 숏 주문을 취소시킬 수 있다.

Keyword

logic flaw, dos

Vulnerability

숏을 NFT화 하여 이동할 때, 만약 해당 숏이 부분 매칭된 숏이라면 숏 주문을 닫는다. 이 때, 이 숏의 주인이 해당 숏 주문의 주인인지를 확인하지 않는다. 또한 NFT 화 한 뒤 숏을 닫아도 해당 NFT는 삭제되지 않는다. NFT에 등록된 숏 데이터 역시 남아있는다. 즉, 숏을 닫아도 nft.shortRecordIdnft.shortOrderId 데이터는 유지된다.

숏 주문은 주문이 취소 또는 완료되면 shortOrderId 가 재활용된다. 만약 숏 NFT를 만들고, 부분매칭 상태였던 nft.shortOrderId 숏주문을 닫는다면 추후 다른 유저가 nft.shortOrderId 와 동일한 ID로 숏 주문을 만들 것이다. 이후 NFT를 이동하면 여전히 nft.shortOrderId 가 해당 숏의 숏 주문이라고 인식, 숏 주문을 닫으려고 시도한다. 숏 주문의 주인을 확인하지 않으므로 남의 숏 주문을 닫아버릴 수 있다.

    function transferShortRecord(address from, address to, uint40 tokenId) internal {
        AppStorage storage s = appStorage();
 
        STypes.NFT storage nft = s.nftMapping[tokenId];
        address asset = s.assetMapping[nft.assetId];
        STypes.ShortRecord storage short = s.shortRecords[asset][from][nft.shortRecordId];
        if (short.status == SR.Closed) revert Errors.OriginalShortRecordCancelled();
        if (short.ercDebt == 0) revert Errors.OriginalShortRecordRedeemed();
 
        // @dev shortOrderId is already validated in mintNFT
        if (short.status == SR.PartialFill) {
@>          LibOrders.cancelShort(asset, nft.shortOrderId);
        }
        ...
    }
  1. 공격자가 부분 매칭된 숏을 만든다
  2. shortRecordId = 2
  3. shortOrderId = 100, 숏 주문 ID 리스트: HEAD > HEAD > 100 > TAIL
  4. 부분 매칭 숏을 NFT화 한다.
  5. shortRecordId = 2 인 NFT가 생성된다.
  6. 숏 주문을 취소한다.
  7. 숏 주문 ID 리스트: HEAD > 100 > HEAD > TAIL (주문 ID 재활용 리스트로 이동)
  8. 숏 종료하기 (예를 들어 exitShortErcEscrowed 호출한다. 민팅된 만큼 dUSD를 지불하면 담보를 돌려받고 숏 레코드를 닫을 수 있다)
  9. 피해자가 숏 주문 생성
  10. 숏 주문 ID로 100 을 재활용 리스트에서 꺼내서 사용
  11. 숏 주문 ID 리스트: HEAD > HEAD > 100 > TAIL
  12. 공격자가 다시 부분 매칭된 숏 생성
  13. shortRecordId = 2, (shortRecordId 는 유저별로 할당되고, 재활용되므로 기존 삭제된 ID를 재활용 한다)
  14. 기존에 만들어둔 숏 NFT 를 transferFrom 을 호출해 이동한다.
  15. 기존에 만든 NFT는 기존 숏 정보를 nftMapping[tokenId] 에 담고 있다. 즉, 기존 숏을 닫았어도 기존 숏 데이터가 남아있다. 즉 nft.shortOrderId 는 여전히 100으로 저장되어 있다.
  16. 공격자는 shortRecordId = 2 인 숏을 새로 생성했기 때문에, 기존 NFT에 매핑된 2번 숏이 있다고 취급되어 각종 상태 확인을 우회할 수 있다. (실제로는 다른 숏이지만 ID 재활용으로 인해 정상으로 판단)
  17. transferShortRecord 에서 부분 매칭 숏 주문 100번을 닫으려고 한다. 이로 인해 피해자의 숏 주문이 닫히게 된다.

공격의 심각성을 강화하기 위해 공격자가 여러개의 숏 주문 ID에 동일한 방법으로 함정을 만들어두고 유저의 주문을 취소시킬 수 있다.

Impact

공격자는 숏 주문을 완전히 제어할 수 있으며, 주문을 검열하고 낮은 가격의 주문을 취소하여 가격을 조작할 수 있다. 리워드 지급 직전에 숏 주문을 취소시켜 다른 사람의 보상을 줄일 수 있다. (리워드는 오더북에서 일정 시간이 지난 후에 지급됨) 유저를 방해할 수 있다. (DoS)

Mitigation

  1. transferShortRecord 에서 부분 매칭된 숏 주문을 닫으려고 할 때, 실제로 해당 숏 주문의 주인인지를 확인한다. (재활용된, 다른 이의 주문이 아닌지 확인)
  2. 숏 레코드를 삭제할 때 연동된 NFT도 소각하도록 한다.

Memo

shortOrderId의 주인을 확인하지 않았다는 점 뿐만 아니라 기존 숏의 NFT가 새로 만든(ID를 재활용한) 숏에 매핑이 되는 점도 원인이다. NFT 재활용은 따로 이슈가 있는 것으로 아는데, 재활용을 통해 남의 숏 주문을 닫는다는 활용을 하여 좀 더 심각도를 올렸다.

ID 여러개에 걸쳐 함정을 파둔다는 아이디어가 괜찮다.


tags: bughunting, dittoeth, smart contract, solidity, logic flaw, dos, nft, solo issue, severity high