Introduction , we learned about the Solidity programming language, smart contract primers and looked at some of the basic hacks and vulnerabilities exploited to drain funds out fo smart contracts. In this article, we’ll take this knowledge a step further by examining real-world hacks and walking through them. In part 1 of this series Example #1: Parity “hack” #1 TL;DR — A vulnerability was found on the Wallet version 1.5+, that allowed an attacker to steal over Parity Multisig 150,000 Ether ($30,000,000 at the time, $105,000,000 today ETH/USD 700). Gavin Woods, one of the original Solidity developers was the CTO of Parity. He actually wrote the code that we are going to examine. One of the things that Parity provided to its users is multisig. Multisig is a wallet with Basically, Parity offered “custodians” out of the box. M owners, and it requires N out of M signatures (confirmations) to do something with these funds. A malicious hacker was able to target a specific multisig wallet, and steal the above mentioned amount. The attacker could have stolen more, if it was not for a white hacker group. The white hackers drained all the wallets they could find with this exploit so that the malicious hacker couldn’t get his hands on more funds. A couple of weeks later the white hackers returned all the funds they had drained. The white hackers saved ~$264 million dollars in todays terms. So what happened here? Parity had a library called WalletLibrary. It was deployed to the Ethereum network and reused by the multisig wallets smart contracts, so that people wouldn’t spend too much gas redeploying the library all the time. This wallet library had nice modifiers, and What’s important to notice is that all these modifiers used state variables to see who is the owner, how many confirmations there are etc. Look at the two snippets below. onlyOwner onlyManyOwners. Then we had this interesting and suspicious function called that was used to initialize the wallets in the constructor. The argument of this function is an array of What stands out here? This function has no visibility explicitly stated! initWallet owners required which represents the number of owners needs for confirmation of transaction. Wait but maybe or has some type of protection or modifier? initDayLimit initMultiOwned No, they don’t have visibility defined either! So basically initWallet can be called by anyone!!!! But, wait … until this point we have examined the , it’s not actually a part of it. So maybe if the Wallet contract is fine this won’t be a problem? Let’s take a look at the Wallet Contract. WalletLibrary. This library is utilized by the Wallet contract You can see that in the Wallet we had a Wallet Library state variable and we have those delegateCalls. delegateCalls Aside: accepts parameters which are encoded msg data, encoded name of a function and encoded params. This is exactly what happened. Focusing on the , we see that it is public. This makes sense because we want anyone to be able to call it. But because the rest of the functions were also public an attacker could This is exactly what happened. The attacker exploited this and simply changed the contract’s state variable to a list containing only their addresses and requiring just confirmation to execute any transaction. fallback function with a payable modifier We can notice that the 3 delegate calls above are all from public methods! invoke delegateCall with encoded data that represents the initWallet function name, data and expected parameters, and set themselves as owner! m_owners one Mitigation Complexity is a vulnerability. Keep it Simple Stupid. Over optimisation and complexity is the root of all evil! Always define visibility explicitly. Don’t extract the constructor logic into the library contract. Avoid premature optimisations! Don’t use delegateCall as a catch-all forwarding mechanism. Mitigation: Parity Developers’ Fix: Parity developers did two things. They declard initDayLimit and initMultiowned to be internal. They added a modifier called that said if I already have owners, then revert. They added this modifier to so essentially it could not be called twice. It looks good… But there is a huge bug! We’ll revisit this later! only_uninitialiszed initWallet, to the patch deployed to fix this hack. The comment thread is interesting! Here is a link Example #2: Rubixi Rubixi is a contract which its implementation is reminiscent of a pyramid scheme allegedly. (Although it is not a pyramid scheme!) Investors can deposit funds. The can collect all of the funds. owner If you bring more people to join this smart contract you get part of their fees. There is a member called creator which is initialized in the constructor. We have the modifier that will only execute code if you are the proper owner. Additionally, we have ways to collect fees based on this modifier. onlyOwner So what’s wrong here?! The original contract name and constructor was DynamicPyramid. The creators had a change of heart and decided that perhaps something with the word Pyramid would not market well. They changed the name to Rubixi and forgot to change the name of the constructor method ;) Because the function was public, anyone could call it and set themselves are the contract creator! There was 1 contract where 100 Ether was lost and another where ~0.1Ether was lost. DynamicPyramid Mitigation Well, for starters, try not to misname functions… Stay vigilant!! The scammers are getting better and better. Starting from you can now use the safe method instead. This means you can do the following instead. 0.4.22, constructor Example #3: Bank, Smart Contract (Reentrance) We have a smart contract called Bank. Each user has balances, and users can deposit funds which will immediately update the state variable with how many funds are available. We can use the function to take out funds, and then these funds are sent to us. withdraw What’s the problem? First, it is important to note that is a Therefore, the balance will not get updated until the assert statement is completed. assert blocking synchronous call. This works well if the depositing entity is a user, but what happens if it is a smart contract? If the entity that deposited funds , then (if it exists or fails). This fallback function will go back to the bank and do another withdrawal before the original transaction is complete. The bank contract guards against this by updating the balance, but this is only after the potential attacker can make two withdrawals. This type of attack (bug?) is called . is a smart contract and not a user sending those funds to the smart contract will trigger a payable fallback function reentrance Let’s look at an example of a malicious smart contract called which will exploit the smart contract via a reentrancy bug. Robber Bank We can only do this twice per contract. But if it works we can create and deploy many contracts and do this repetitively until we drain the bank! Mitigation #1: Checks-Effects-Interactions Pattern (who called the function, are the arguments in range, did they send enough Ether, does the person have tokens, etc.). Perform checks If all checks passed, . effects to the state variables of the current contract should be made Just to make the example clearer, this is where our contract failed. It interacted with the smart contract before it completed all state variables. Lastly, perform any interaction with other accounts/contracts. Robber effecting By utilizing this pattern we can ensure that even if there is a issue, the will fail. So following this pattern the fix for our will be first effecting state variables — in this case, the balance, and then calling to the smart contract/user: reentrance check Bank contract Mitigation #2: Avoid call.value()() In Ethereum there are 3 ways to interact with other smart contracts implicitly. When sending Ether, we should be aware of the relative tradeoffs between the use of them: will send the provided Ether and trigger code execution given if the user / smart contract sent enough gas he could complete executing the reentrance. address.call.value(): all available gas. So, in Bank / Robber example, will send the provided Ether and trigger code execution given a limited stipend of This is similar yet the gas is capped and is enough for basically always receiving funds. Anything more complicated would fail with an . address.send(): 2,300 gas. Out of Gas exception is equivalent to It will automatically revert if the send fails. address.transfer(): require(address.send()). Example #4: “The DAO” The “DAO” is the name of a particular DAO (Decentralized Autonomous Organization), conceived of and programmed by the team behind German start Slock.it, a company building “smart locks” that let people share their things (cars, boats, apartments) in a decentralized version of Airbnb. It would as a to finance DApps (decentralized applications) wherein participants could vote to determine which DApps received funding. decentralized venture capital fund It was launched on April 30th, 2016, with a 28-day funding window. It was the largest crowdfunding in history, having raised over $150,000,000 (with today's prices it is a couple of billions) from more than 11,000 enthusiastic members. On June 18th, 2016 the attacker started to drain “The DAO” using a relatively sophisticated reentrancy attack. The attacker has managed to drain more than ETH/USD 700) 3,600,000 Ether ($72,000,000 at the time; astounding $2,520,000,000 today How did the Ethereum community respond? The community responded by splitting into two, aka ! Those who believed that the hack was legit ( stayed on the network and came to be known as The forkers formed a new network where they restored all the lost money and became known as (plain-old) Ethereum. hard forking code is law) Ethereum Classic. Example #5: Honeypot Here we have an Asset smart contract. This is a toy example to demonstrate the concept so bear with me ;) Originally the creator of the Asset contract is the owner of this asset. Anyone can send funds to this contract. If someone outbids the amount of funds that reside in this smart contract, then you become the owner of this asset and receive all the funds of the asset. What are the cases where the ? if statement can hold true This will never be true! This is because the balance is updated with the value this code is ever reached. This type of contract had 2 version, where the first one drained 20 Ether and the second drained 5 Ether, from people who bid before ‘’Alternative” Ether Transfers Until now we’ve examined explicit methods for transferring Ether. Let’s look at some options for transfers which are less known / popular. In addition to the regular means to send Ether (e.g. call, send/transfer), there are two more ways which bypass the fallback function. : The only possibility that code is removed from the is when a contract at the address performs the operation (previously called selfdestruct receives an address parameter which specifies where to move the funds of the contract to be destroyed. selfdestruct blockchain selfdestruct suicide). This is incentive to implement a self destruct call to remove contracts from the network that are no longer useful. If the receiving address is a its fallback function This is a weird corner case/ choice in Solidity. contract, does not get executed. design As a miner, set the target address as the coinbase address in order for it to receive block mining rewards. Let’s see an example. Every time someone sends funds to the contract we will selfdestruct and send the funds to an address determined in the constructor. GenerousAttacker GenerousAttacker So every time a user transfers money to this contract, the value stored in the contract is transferred to a pre determined target, without explicitly transferring. Beware! Without knowing the language thoroughly you could read through this and think that it is innocent! Mitigation: Beware of Assumptions Never use a contract’s balance as a guard In general, be mindful of language/framework specific features and updates. Beware of compiler optimizations and bugs and test accordingly. Beware of compiler specific bugs and always use strict compiler version. Beware of potential miners’ intervention (eg. front-running, chain re-org, etc). Example #6: Parity “hack” #2 TL;DR: Somebody opened a Github issue with the Parity wallet, and said “hey I accidentally killed it”. Essentially he figured out a way to delete the smart contract. Suddenly, their were many wallets that were using a library that could be deleted and cease to exist! Approximately 513,000 ETH had been locked in the affected contracts. No funds were “stolen”; only made unreachable, by an accident. There are a few proposals for methods to restore the lost funds, and even for a new governance model, but it’s unlikely to happen any time soon. So what happened here? If you recall we had this You can see the library uses many state variables. But where are these state variables declared and initialized? only_unitialized modifier. We search all the contracts used to create the parity wallet and find that these state variables are defined in the Wallet Contract. The contract contains state variables that it expects to be by the calling contract’s own state. WalletLibrary shadowed Once deployed, the WalletLibrary contract is simply uninitialized, so is 0. m_numOwners If the WalletLibrary isn’t executed in a allowing anyone to call methods that this modifier guards, one of which is . Wallet contract’s context, m_numOwners is 0, initWallet How was this exploited? A developer invoked and set himself as the only owner, and then proceeded to kill it by invoking the function. The developer who found this bug had a Github handle of and then tweeted this initWallet kill devops199 Example #7: Auction Contract So a user can make an Auction bid, and if it’s the highest they get the funds. But what happens if the methods fails? transfer Imagine, just as the auction starts we make a cheap bid and become owners. Then, if someone outbids us and we somehow make the transfer fail then we become the Auction leaders forever but still hold on the new highest bid! So let’s take a look at how this would work. Let’s simply create a contract that doesn’t implement a payable fallback function, thus making it impossible to send us funds. Mitigation #1: Favor Pull over Push Always remember that you’re not only interacting with human beings, but also with other contracts. We can additionally mitigate this by implementing a refund mechanism. Essentially, we will hold a map of the funds that each user contributed and implement a function that allows everyone to request their funds back. withdraw Mitigation #2: Ignore Contracts It's usually not recommended or desired, but it’s also possible to opt-out from interacting with the contract using the following check: Conclusion Smart contract is a term that is often thrown around synonymously with Although smart contracts remove middlemen, and centralized contract enforcers, these hacks prove that there is indeed an element of trust still involved. Instead of trusting a corporation or a fleet of lawyers, we lay our trust in code and the developers who write and audit these contracts. trust less environments. Although the code is error-prone, I believe that the smart contract community will continue to improve their proficiency in writing smart contracts. Developers are becoming more experienced, learning from past mistakes, and better tools are constantly being developed to aid in analyzing contract logic/testing. I’m excited by the future smart contracts hold and believe they will grow in popularity in the coming years. My next posts will explore smart contracts and distributed application development (dapps), so stay tuned! If you’re looking for resources to ramp up on the world, I highly recommend (again) checking out the , as it has a wonderful inventory of high quality, technical discussions and lectures. Huge thanks to for building this lecture and teaching it at Blockchain Academy! blockchain Kin Ecosystem Youtube channel Leonid Beder If this post was helpful, please subscribe and click the clap 👏 button below to show your support! ⬇⬇ You can follow me on , , and . Instagram Linkedin Medium