Cover photo

Bug Bounty 0x01 - Just for Approval Purposes

An overview of a vulnerability found in Civic Pass Platform, which allowed me to steal user's funds

A wild writeup appeared (after sometime)!

In this writeup I will talk a little about a vulnerability that I found during a hunt in which it allowed an attacker to steal tokens from any user who previously approved the protocol's Smart Contract to spend their assets.

Target: Civic Pass
Platform: HackenProof
Date: 09/21/2023

Target Overview

"A Civic Pass is a non-transferrable token that represents a user’s identity and lives in their wallet.
Civic Pass serves as the gatekeeper for your dApp, allowing for access control based on requirements such as age, location, humanity and more." - civic.com

There is a product called Civic ID, which allows users to get a digital identity that can be verified across some blockchains.

Bug Overview

Let's explain how the protocol itself works:

  1. To allow users to create a digital identity for it, app owners must create a new network, and further, mint the token (an ERC3525) for this network.

  2. Users have unique tokens on the network to verify its identity.

So, let's see the createNetwork function:

After created, the network owner can define gatekeepers (allowed users to mint tokens), that will mint new tokens for the users.

Now, the gatekeeper can mint new tokens for the users, and the users must pay a fee for the mint:

Also, the Charge object

I think that at this time, I think that you've already identified the vulnerability 😆

But wait... How an user can pay the fee of the minting if it isn't the gatekeeper (and also doesn't have privileges to mint)?

Well, you (in this case, the users) have the possibility to allow anyone to spend your funds (a portion or everything) trough the allowance feature.

How allowance works?

Inside tokens, besides a mapping of stored balances, there is also a mapping of allowances, which stores who allowed who (and the amount) to spend their funds.

Allowances are used to enable users to interact with smart contracts which need to use their funds, for example, a Liquidity Pool, which need to get the tokenA of the user, deposit into the Pool, and further send back the tokenB. So, the user needs to approve the Liquidity Pool to spend their tokenA to receive back the tokenB, where the Liquidity Pool will transfer the funds from the user trough the transferFrom function to the Pool itself.

Lastly, the user also can disapprove the Liquidity Pool to spend their tokens if it doesn't have plans to use again the Liquidity Pool.

Ok, and the vulnerability?

So, the vulnerability occurs because you can specify ANY ADDRESS and ANY AMOUNT which will pay for the fee, and the allowance is not consumed after the execution, allowing an attacker to supply addresses of users that approved the Civic contract to spend their funds.

To exploit this vulnerability, the attacker must search for users which approved the Civic contract to spend their funds. A way to do this, is by looking into another networks and getting the user's addresses.

I've developed a simple PoC to exploit the vulnerability, the code can be found here:

The following actions are being performed in the code:

  1. 186-188: Simulating the victim approving the civic contract (gatewayToken) to spend this entire balance (this can be done if the user minted a token of another network for example)

  2. 194-195: The attacker creates a new network

  3. 196-202: Charge object with the correct parameters. Here, the tokenSpender (address which will pay the fee) is the victim's address, the recipient (address which will receive the fee) is the attacker address, the value (fee amount) will be the entire balance of the victim.

  4. 204: Adding itself as gatekeeper to mint new tokens

  5. 206: Minting a new network token to the victim and supplying the charge object to the function.

The output of the execution (with some prints modifications) can be found below:

As we can see, the civic contract is allowed to spend 19807815544451337193658 tokens from the victim (717), where these tokens are posteriorly transferred from the victim to the attacker (26848) trough the transferFrom function.

The Fix

To fix this vulnerability the Team implemented a local approval mechanism, where the user must approve the civic contract to spend their token in a specific network, so, malicious networks can't directly transfer the funds of the users, as it needs to pass into the checks

Also, the local approval

Thats all!

Finally we reached the end of this simple writeup, I think I've achieved to share how the entire flow (identify, exploit and fix) for this issue was been made.

If you read until here, I want to say thank you!

rapt00r's Lab logo
Subscribe to rapt00r's Lab and never miss a post.
#solidity#web3#bugbounty#writeup#ethereum