Case Studies of Real-World Smart Contract Vulnerabilities and Exploits
Smart contracts have the potential to revolutionize the way we conduct transactions and automate processes, but they are not immune to vulnerabilities and exploits. In this article, we will explore some real-world examples of smart contract vulnerabilities and the consequences they had.
Case Study: The DAO Hack
In 2016, an organization called The DAO (Decentralized Autonomous Organization) raised over $150 million through a crowd sale of its own cryptocurrency, called DAO tokens. The DAO was essentially a venture capital fund that was run entirely on smart contracts on the Ethereum blockchain.
Unfortunately, a hacker discovered a vulnerability in The DAO’s smart contract code that allowed them to drain $50 million worth of DAO tokens. The hack caused a major controversy and ultimately led to the creation of Ethereum Classic, a separate cryptocurrency that retained the original version of the Ethereum blockchain (without the hack).
Here is an example of the vulnerable code that was used in The DAO’s smart contract:
function transferFrom(address _from, address _to, uint _value) public {
require(_to != address(0));
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
}
The vulnerability in this code occurred in the third require
statement, which checked that the _value
being transferred was less than or equal to the amount that was allowed[_from][msg.sender]
. However, the allowed
variable was not properly updated when the transferFrom
function was called repeatedly in a short period of time (a technique known as a “reentrancy attack”). This allowed the hacker to drain a large amount of DAO tokens before the vulnerability was detected.
Case Study: Parity Wallet Hack
In 2017, a hacker exploited a vulnerability in the smart contract code of the Parity Wallet, a popular Ethereum wallet developed by Parity Technologies. The hack resulted in the theft of over $30 million worth of Ethereum.
The vulnerability in the Parity Wallet’s smart contract code was related to the way it handled the assignment of ownership of the wallet. The code contained a function that allowed the owner of the wallet to assign a new owner, but it did not properly check that the new owner was not already an owner of another wallet. The hacker was able to use this vulnerability to assign themselves as the owner of multiple wallets, thereby stealing their funds.
Here is an example of the vulnerable code from the Parity Wallet’s smart contract:
function changeOwner(address _newOwner) public {
require(msg.sender == owner);
owner = _newOwner;
}
Case Study: Bancor Network Hack
In July 2018, the Bancor Network, a decentralized exchange platform, was hacked and $13.5 million worth of cryptocurrency was stolen. The hack was made possible due to a vulnerability in the smart contract code of the Bancor Network’s wallet contract.
The vulnerability allowed the attacker to execute a “transferFrom” function on the contract, which allowed them to transfer tokens out of the wallet without the owner’s permission. This function should have been protected by an access control, but it was left open, allowing the attacker to exploit it.
To prevent similar vulnerabilities, it is important to properly implement access controls and thoroughly test smart contract code before deployment. In the case of the Bancor Network, the lack of proper testing and review of the code led to the loss of a significant amount of funds.
Conclusion
These are just a few examples of the potential vulnerabilities and exploits that can occur in smart contracts. It is essential for developers to thoroughly test and review their code to ensure the security of their contracts. It is also important for users to be aware of the potential risks and to only use trusted and secure smart contracts.
Exercises
To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.
Write a function to determine if a smart contract has a fallback function that can be exploited by an attacker.
function hasFallbackFunction(contract) public view returns (bool) {
assembly {
// Retrieve the contract's bytecode
let code := mload(0x40)
// Check if the bytecode contains the fallback function's signature
if extcodesize(contract) > 0 {
let size := extcodesize(contract)
let data := mload(0x40)
let offset := 0
let matched := 0
// Loop through the contract's bytecode
for {
let codeByte := mload(add(data, offset))
if eq(codeByte, 0x61) {
let nextByte := mload(add(data, add(offset, 1)))
if eq(nextByte, 0xfe) {
matched := 1
}
}
if eq(add(offset, 1), size) {
break
}
offset := add(offset, 1)
}
if eq(matched, 1) {
return 1
} else {
return 0
}
}
}
return 0
}
Write a function to check if a smart contract has an “unchecked send” that could potentially result in an attack.
function hasUncheckedSend(contract) public view returns (bool) {
assembly {
// Retrieve the contract's bytecode
let code := mload(0x40)
// Check if the bytecode contains the "unchecked send" function's signature
if extcodesize(contract) > 0 {
let size := extcodesize(contract)
let data := mload(0x40)
let offset := 0
let matched := 0
// Loop through the contract's bytecode
for {
let codeByte := mload(add(data, offset))
if eq(codeByte, 0x73) {
let nextByte := mload(add(data, add(offset, 1)))
if eq(nextByte, 0x10) {
matched := 1
}
}
if eq(add(offset, 1), size) {
break
}
offset := add(offset, 1)
}
if eq(matched, 1) {
return 1
} else {
return 0
}
}
}
return 0
}
Write a Solidity function that checks if a given address is a contract.
function isContract(address _addr) public view returns (bool) {
uint size;
assembly {
size := extcodesize(_addr)
}
return size > 0;
}
Write a Solidity function that checks if a given contract has a fallback function.
function hasFallback(address _addr) public view returns (bool) {
bytes4 sig = bytes4(keccak256("fallback()"));
return assembly {
let result := delegatecall(_addr, add(sig, 0x20), 0, 0, 0, 0)
switch result
case 0 {
return 1
}
default {
return 0
}
}
}
Write a Solidity function that checks if a given contract has an externally owned account (EOA) as an owner.
function hasEOAOwner(address _addr) public view returns (bool) {
bytes4 sig = bytes4(keccak256("owner()"));
address owner;
assembly {
owner := call(_addr, sig, 0, 0, 0)
}
return owner.isContract() == false;
}