The bugs sometimes hide in interesting places. Hybrid attacks are very interesting things, where projects rely on on-chain data that may be actually modified.
I wrote about Ocean Protocol design before, in this article, so you can use it for some extra explanations.
Let’s take e.g. this dataset. If you will enable debug mode, you will see its Data Description Object, or DDO (maybe it’s Data Description, but you got the idea) that looks like JSON below. It is mostly stored on a blockchain, and can be manipulated during creation. Maybe during the editing too, but I’m unsure here - didn’t try it then.
{
"@context": ["https://w3id.org/did/v1"],
"id": "did:op:fa0e8fa9550e8eb13392d6eeb9ba9f8111801b332c8d2345b350b3bc66b379d5",
"nftAddress": "0xBB1081DbF3227bbB233Db68f7117114baBb43656",
"version": "4.1.0",
"chainId": 137,
"metadata": {...},
"services": [
{
"id": "24654b91482a3351050510ff72694d88edae803cf31a5da993da963ba0087648",
"type": "access",
"files": "0x04...86c",
"datatokenAddress": "0xfF4AE9869Cafb5Ff725f962F3Bbc22Fb303A8aD8",
"serviceEndpoint": "https://v4.provider.polygon.oceanprotocol.com",
"timeout": 604800
}
],
"event": {...},
"nft": {...},
"purgatory": {
"state": false
},
"datatokens": [...],
"stats": {...},
"accessDetails": {...}
}
What is interesting here is that file itself is detached from NFT. It is not one-to-one mapping somewhere, but separate and manipulatable record.
So, what if just use someone else’s data ID?
After several attempts of picking someone else’s data, I was able to figure out that no consistency check is happening, and if services[*].files
is modified to files
from any other dataset, it literally downloads someone else’s file.
Rephrasing, you can create a dataset that is giving you not yours files, but files of victim. Yes, that simple.
Since I had no experience with web3 at that moment I was not able to create a normal report, so behold, the most terrible valid PoC you ever seen:
So, how it’s working? let’s have a look on a PR that fixes this bug.
We can see that indeed, there was missing consistency checks. Now they are there:
if Web3.toChecksumAddress(files_json["datatokenAddress"]) != Web3.toChecksumAddress(service.datatoken_address):
raise Exception(...)
if Web3.toChecksumAddress(files_json["nftAddress"]) != Web3.toChecksumAddress(asset.nftAddress):
raise Exception(...)
So simple, yet missed.
This was my first crit, and I’m still very proud of discovering it; lots of kudos goes to Ocean for incredibly good attitude and fast speed (4 days from report to confirmation, 2 days more to payout).
I love finding this kind of bugs - needs a lot of time to discover and understand, but after you get it - it looks so simple to you; like a Optimism bug. No heavy math, no flashloans and MEV, just something that everyone simply missed.
Subscribe to my Twitter for more web3 security stuff: