VerificationType is not enforced for claiming NFT directly with signatureClaim()
c4-bot-8 opened this issue · 0 comments
Lines of code
Vulnerability details
Impact
When an artist creates art, they can set the verificationType. If the verificationType is set to "Signature", claims using merkle trees directly via merkleClaim() or claim() cannot be executed, as no merkleRoot is expected to be added when creating art.
However, the flow is missing a check in signatureClaim(), so it's possible to use both MERKLE and SIGNATURE for proof when verificationType is set to MERKLE.
Proof of Concept
Check here how validationType is set upon art creation.
We can see how its enforced, when using claim():
function claim(bytes calldata encodeData_) external payable {
bytes memory encodedata_ = LibZip.cdDecompress(encodeData_);
(uint256 artId) = abi.decode(encodedata_, (uint256));
PhiArt storage art = arts[artId];
uint256 tokenId = IPhiNFT1155Ownable(art.artAddress).getTokenIdFromFactoryArtId(artId);
@>> //@audit enforcing type based on the verificationType saved in the art
@>> if (art.verificationType.eq("MERKLE")) {
.....
@>> } else if (art.verificationType.eq("SIGNATURE")) {
.....
} else {
revert InvalidVerificationType();
}
}Its also enforced when we use merkleClaim :
function merkleClaim(....) {
(address minter_, address ref_, uint256 artId_) = abi.decode(encodeData_, (address, address, uint256));
PhiArt storage art = arts[artId_];
bytes32 credMerkleRootHash = credMerkleRoot[art.credChainId][art.credId];
@>> if (minter_ == address(0) || !art.verificationType.eq("MERKLE") || credMerkleRootHash == bytes32(0)) {
revert InvalidMerkleProof();
}
......However, such verification does not exist in signatureClaim() allowing us to claim with both signatures and merkle when calling signatureClaim() instead of claim(), even though the verificationType is explicitly set to "MERKLE":
Verify with code-snippet here
Tools Used
Manual Review
Recommended Mitigation Steps
Enforce verificationType with a explicit check:
function signatureClaim(
bytes calldata signature_,
bytes calldata encodeData_,
MintArgs calldata mintArgs_
)
external
payable
whenNotPaused
{
(uint256 expiresIn_, address minter_, address ref_, address verifier_, uint256 artId_,, bytes32 data_) =
abi.decode(encodeData_, (uint256, address, address, address, uint256, uint256, bytes32));
+ PhiArt storage art = arts[artId];
+ if (!art.verificationType.eq("SIGNATURE")) revert;
if (expiresIn_ <= block.timestamp) revert SignatureExpired();
if (_recoverSigner(keccak256(encodeData_), signature_) != phiSignerAddress) revert AddressNotSigned();
_validateAndUpdateClaimState(artId_, minter_, mintArgs_.quantity);
_processClaim(artId_, minter_, ref_, verifier_, mintArgs_.quantity, data_, mintArgs_.imageURI, msg.value);
emit ArtClaimedData(artId_, "SIGNATURE", minter_, ref_, verifier_, arts[artId_].artAddress, mintArgs_.quantity);
}Assessed type
Context