Mental Models for Solidity Exploit Detection
Recently I’ve been studying to become a Solidity auditor. My primary reason for doing this is I don’t believe there will be fewer smart contract bugs in the future, and therefore this skill will be in high demand for as long as Solidity is the primary language of smart contract development.
While studying for some upcoming interviews, I’ve gone through a number of different security tutorials, challenges, and post-mortems. After all of this studying, I thought it would be beneficial to recap what I’ve learned. This post is primarily for my own benefit to recap what I’ve learned in the last few weeks, but, since I’m doing it anyway I will also post it in case anyone else finds value in it.
In general, I’ve found there to be 5 classes of Solidity exploits. Each exploit has a certain “code smell” associated with it. If you can detect the code smell, usually you’re more than halfway to the exploit, as it’s a case of figuring out what variation of a well-known exploit the code smell is suggesting and implementing it. There are already countless posts about the most common Solidity exploits, so why write another? Well, typically these posts are written for Solidity developers, and not auditors. For this reason, they start with the exploits and work back to the code examples. In this post, I would like to do the opposite and start with the code smell first, because as an auditor, you’re looking at code that has already been written, rather than writing the code yourself.
The first code smell is the use of dynamic arrays. The exploit associated with dynamic arrays is well-documented and well-known. This is the Solidity analog to the old C buffer overflow exploits. If a dynamic array is used in a smart contract, you can underflow it and get arbitrary access to all of the storage variables of a smart contract. This allows you to change balances, take ownership of a contract, or anything else related to contract variables. Note, this can also be used to allow an attacker access to related or dependent smart contract storage depending on how those related contracts are imported or called.
The key takeaway is, if you see a dynamic array being used in a smart contract, always look to see if it can be underflowed.
Example Challenges
https://ethernaut.openzeppelin.com/level/0xda5b3Fb76C78b6EdEE6BE8F11a1c31EcfB02b272
https://capturetheether.com/challenges/math/mapping/
The second code smell is any sort of ratio mathematics. The overwhelming majority of smart contracts are using unsigned integers, so this opens you up to floating point arithmetic errors. However, aside from this, it makes you susceptible to flash loan attacks or other forms of unsigned integer manipulation.
The key takeaway is, if you see ratios being used in smart contracts, look to attack them using flash loans, or floating point arithmetic.
Example Challenges
https://ethernaut.openzeppelin.com/level/0x0b0276F85EF92432fBd6529E169D9dE4aD337b1F
https://ethernaut.openzeppelin.com/level/0xd2BA82c4777a8d619144d32a2314ee620BC9E09c
https://www.damnvulnerabledefi.xyz/challenges/8.html
This is a classic re-entrancy exploit. The exploit is not always as obvious as it was in the DAO hack. Any time a balance is set prior to an external call, or, a balance is incremented or decremented after (rather than before) an external call, look for re-entrancy attacks. Sometimes these are much harder to spot than you would expect.
The key takeaway is: any time there are balance changes in the same function where there are external calls think through whether a re-entrancy attack is possible or not.
Example Challenges
https://ethernaut.openzeppelin.com/level/0x848fb2124071146990c7abE8511f851C7f527aF4
If you’re asked to find a private key for an admin address, the question almost always revolves around finding some transactions from the private key in question with a repeated r value used in the signatures. You can typically tell when you’re being asked about this kind of exploit by virtue of the fact that you’ll need to look outside the smart contract (HTTP traffic, Etherscan, oracle requests, etc) for some sort of information. Always check if the transactions use the same r value when this is the case.
Example Challenges
https://www.damnvulnerabledefi.xyz/challenges/7.html
https://capturetheether.com/challenges/accounts/account-takeover/
Often tokens are used for accounting in various ways in other contracts. When this is the case, think through whether you can manipulate the balance in the parent contract by transferring the tokens directly between parties, using fake or nefarious token contracts, or by taking out flash loans.
Remember, just because a parent smart contract is using a token for accounting doesn’t mean you can’t access the token’s contract directly, bypassing any controls in the parent contract.
Example Challenges
https://www.damnvulnerabledefi.xyz/challenges/1.html
There are of course, many more Solidity code smells than these. I left these out because I couldn’t think of a good way of condensing them into one particular code smell, or because they’re already well known or trivial. In no particular order:
Determinism: Nothing is non-deterministic in smart contracts. So, if you see someone trying to generate randomness in a smart contract, it’s exploitable, either directly, or probabilistically.
If you’re a company hiring smart contract auditors, you’re a blockchain start-up looking to have your project audited, or are looking for a Solidity developer who’s well-versed in security, feel free to DM me on Twitter, or message me on LinkedIn.
Also published here.