Oracles play a critical role in many DeFi applications where they are used to correctly report asset prices and other data. As evident by many incidents such as Cheese Bank and Warp Finance, any oracle price manipulations can lead to multi-million losses. The next Damn Vulnerable DeFi challenge offers a plausible scenario where a price Oracle platform appears to leak potentially sensitive data:
While poking around a web service of one of the most popular DeFi projects in the space, you get a somewhat strange response from their server. This is a snippet:HTTP/2 200 OK
4d 48 68 6a 4e 6a 63 34 5a 57 59 78 59 57 45 30 4e 54 5a 6b 59 54 59 31 59 7a 5a 6d 59 7a 55 34 4e 6a 46 6b 4e 44 51 34 4f 54 4a 6a 5a 47 5a 68 59 7a 42 6a 4e 6d 4d 34 59 7a 49 31 4e 6a 42 69 5a 6a 42 6a 4f 57 5a 69 59 32 52 68 5a 54 4a 6d 4e 44 63 7a 4e 57 45 35
4d 48 67 79 4d 44 67 79 4e 44 4a 6a 4e 44 42 68 59 32 52 6d 59 54 6c 6c 5a 44 67 34 4f 57 55 32 4f 44 56 6a 4d 6a 4d 31 4e 44 64 68 59 32 4a 6c 5a 44 6c 69 5a 57 5a 6a 4e 6a 41 7a 4e 7a 46 6c 4f 54 67 33 4e 57 5a 69 59 32 51 33 4d 7a 59 7a 4e 44 42 69 59 6a 51 34A related on-chain exchange is selling (absurdly overpriced) collectibles called "DVNFT", now at 999 ETH eachThis price is fetched from an on-chain oracle, and is based on three trusted reporters: 0xA73209FB1a42495120166736362A1DfA9F95A105,0xe92401A4d3af5E446d93D11EEc806b1462b39D15 and 0x81A5D6E50C214044bE44cA0CB057fe119097850c.You must steal all ETH available in the exchange.
It’s not clear what those two hex blobs correspond to but let’s run a few experiments. Notice that all of the hex numbers are in the ASCII range between 0x20 and 0x7D. Let’s convert it inside the IPython console:
In : data = "4d 48 68 6a 4e 6a 63 34 5a 57 59 78 59 57 45 30 4e 54 5a 6b 59 54 59 31 59 7a 5a 6d 59 7a 55 34 4e 6a 46 6b 4e 44 51 34 4f 54 4a 6a 5a 47 5a 68 59 7a 42 6a 4e 6d 4d 34 59 7a 49 31 4e 6a 42 69 5a 6a 42 6a 4f 57 5a 69 59 32 52 68 5a 54 4a 6d 4e 44 63 7a 4e 57 45 35"In : bytes.fromhex("".join(data.split())).decode("utf-8")
The purpose of this string is not yet clear, but its alphabet and length (divisible by 4) resembles base64 encoding:
In : base64.b64decode(hexdata)
The hexadecimal value above is 32 bytes long. It’s too long to be an Ethereum address, but we may get lucky if this is the private key. Let’s attempt to import it using web3 and see if it corresponds to any interesting addresses:
In : from web3.auto import w3In : acc = w3.eth.account.privateKeyToAccount('0xc678ef1aa456da65c6fc5861d44892cdfac0c6c8c2560bf0c9fbcdae2f4735a9')In : acc.address
Aha! 0xe92401A4d3af5E446d93D11EEc806b1462b39D15 corresponds to one of the oracle addresses specified in the challenge. Performing the same operation with the second blob, confirms the private key for the other 0x81A5D6E50C214044bE44cA0CB057fe119097850c oracle. Having these keys will allow us to craft arbitrary transactions on behalf of these two oracles. But first let’s look at the smart contracts in this challenge and see how this may be useful:
Exchange.sol implements the token purchase logic:
The exchange implements a custom buy function which obtains the NFT price from the oracle object using
getMedianPrice() method. The method is implemented in TrustfulOracle.sol which computes a median price from multiple oracle sources:
The compromised.challenge.js deploys only three oracles, two of which are now under our control:
const sources = [
If we could report false prices with 2/3 oracles, then the logic used to calculate the median price above will essentially report whatever price we want to. The final piece of the puzzle is to observe the mechanism used to update oracle prices:
It appears that it is up to individual oracles to call the postPrice method with an updated price. This works for us since we have private keys and can craft any transaction. Let’s write an attack script which reports first extremely low and later extremely high prices so that we could empty the entire exchange balance in one go:
In the script above we first set the DVNFT price to 0 which allows an attacker purchase a token for free. Next we set the DVNFT price to the entire exchange balance and sell the previously purchased token to steal all of the funds.
Running the above script produces the following output:
Great! While the example is a bit extreme with the private keys leaking on the oracle’s web server, it is more practical than it seems since oracle prices are often manipulated using flash loans and other techniques.