Attacks on Ethereum Smart Contracts

Attacks on Ethereum Smart Contracts


Hey everyone!
This is David Wong, and in this video I will
talk about attacks on Ethereum smart contracts.
And I’m not making things up,
I’m just giving you a tl;dr of this paper called
“A survey of attacks on Ethereum smart contracts”.
It’s written by Nicola Atzei, Massimo Bartoletti
and Tizianna Cimoli from the university of
Cagliari (Italy)
So if you want more details, be sure to check
their paper.
Before I start showing off real world examples,
actual contracts that got owned, I’m going
to list some of the problems that can surprise
developers of smart contracts.
The first one is named “Call to the unknown”.
Coding with Solidity allows you to call external
contract’s functions.
And you probably know the send() function which
allows you to send money to someone’s address.
But what you might not know is that it also
can lead to external code execution.
In Ethereum, an address can map to someone’s
wallet, but it can also be tied to a contract.
In this case, the fallback function of that
contract will be executed as you’re sending
ether to the address.
That might surprise more than one developer.
Another function is call(), which allows
you to invoke another function located in
another contract or the current contract.
For example here, I call the contract c’s
`ping()` function by its signature, which
is the first 4 bytes of its hash declaration.
Notice here that I said “hash” instead of
“SHA-3”, this is because it’s not really SHA-3,
it’s rather Keccak with an incorrect number
of rounds, blablabla, that’s a story for another day.
`n` here is the argument to the function ping,
and you can see that I am also sending some
ether as part of the call via the “amount”
argument.
In the case where the function with the given
signature does not exist (in the contract `c`)
then the fallback function of `c` will
be called instead (with no arguments).
`delegatecall()` is a similar function to `call()`
(with some subtle but dangerous differences, I’ll talk about that later)
and it has the same call-to-the-unknown problem.
A usually better way to call a function is
to have it declared by name as part of an
interface or a contract, and call the name
directly in your code.
But if we mistyped the declaration (in the interface or the contract),
or if the address that you’re calling end up
not having such a function, the fallback (again) will be run instead.
There are several situations when an exception
is raised in Solidity: if the execution runs
out of gas (you get an out-of-gas exception), if the function manually throws
(by using the throw function, which is now deprecated,
so you can use the revert, require or assert function to throw an exception), if
the call stack reaches its limit (so, by that I mean when you
call a function, this function can call another
function, and on and on, and every time you do this you have these nested calls
and you cannot go further than a depth
of 1024). This used to be a problem and I’ll talk about that later, but it’s not really a problem anymore because this has been fixed in more recent versions.
But anyway, let’s look at an example, Bob here calls Alice’s `ping()` function directly.
And this `ping()` function throws, if it throws it will propagate (or “bubble-up”
as they say) and any changes that took place
in the execution will be reverted.
So here `x` will still be `0` in Bob’s contract.
Now if Bob had called Alice’s `ping` function
via a `call()` (instead of calling it directly),
the throw would not have propagated and the
`EVM` (the Ethereum Virtual Machine) would
have subsituted a `false` as the return value
to the call to Alice’s `ping`.
Therefore, the execution would have continued,
setting `x` to `2` after finishing.
In general, all the low level functions will
behave the same way.
So beware of `call()`, `delegatecall()`, `callcode()`
and `send()` (which is compiled the same way
as a `call()` but with an empty function signature).
Avoid these unless you can’t, and prefer the
function `transfer` which provide a similar
behavior to the `send()` function, but it
does propagate a `throw` if it fails.
So if you cannot avoid that, make sure that you check the return value of these functions for a false return value
And if it returns false, for example you can throw again (via require, assert or revert)
So in the event where you have a series of
nested calls, a `throw` will propagate all
the way up, up until a `call()` or `delegatecall`
(or any of these low level functions) etc…
is reached. And then it wil return a false and if it’s not checked it won’t propagate.
So if you don’t have any of these, the execution
will revert itself successfuly.
If you have any of these low level functions,
be careful.
I said earlier that when you use the `send()`
function to send some ethers to some address,
the fallback function of “some address” (if
it exists) will get executed.
The amount of gas unit available to the callee
is bound to 2300 units.
With 2300 units of gas, not so much can happen,
certainly not a state changing execution,
and if too many things need to be run the
execution will throw with an out-of-gas exception.
In the example here, calling `pay` with the
address of D1 will throw, `send()` will return
`false` (as we talked about previously) and
the ethers will not be paid.
calling it with the address of D2 will be
fine.
This one is similar to the “call-to-the-unknown”.
So, let’s consider the following example:
Here callers to Bob’s `pong` function can
provide an address as argument
Hopefully the address of an `Alice` type of contract.
It doesn’t have to be though!
And like any best-practices guidelines, never
trust user input.
If `c` is not a contract address, `pong` returns
with no execution of code.
If `c` is the address of any contract (not
only a contract of type `Alice`) that implements
a `ping` function that takes a `uint` as argument, then the function will get executed.
Not matter how its behavior differs from the
original `Alice` contract.
If `c` is a contract that doesn’t implement
`ping()` (this function with the same signature),
then the fallback function gets executed.
So Never trust user input.
Let’s observe the following contract.
Anyone can run the `ping` function with an
address as argument.
The `ping` function will send 2 ethers via
a call with no limit on the unit of gas.
And once the 2 ethers have been sent, the `sent`
boolean is toggled and the function cannot
be called again.
Now imagine that an adversary publishes the
following malicious contract
The adversary will provide the address of
his malicious contract to Bob’s `ping` function.
The `ping` function will send 2 ethers to
the Mallory contract, and as we’ve seen previously
this will trigger the execution of the fallback
function inside of the malicious contract.
This function calls Bob’s ping() function again,
which will work since the former execution
has not had time to finish and has not yet
set the variable `sent` to `true`.
Thus, it will successfuly re-send 2 ethers
to the same address and re-run the address’
fallback function.
This goes on and on until either the contract Bob goes out of money OR the execution runs
out of gas OR the maximum depth of the call stack has been reached.
In all cases, an exception will be throwned,
will cancel the last call, but will only propagate
up until the last use of the `call()` function
as we’ve seen in “exception disorder”.
This problem is called “re-entrancy” and has surprised everyone in the Ethereum community and has been quite
an annoying problem for legitimate smart contracts.
This one is pretty straight forward, fields
in contracts can be public or private.
But if you know how the blockchain works,
you know that everything is REALLY public.
The `private` type can be deceptive.
While you can’t directly read the values out
of the contract, you can look at the transactions
and infer what the `private` values are from
them.
Once a contract is in place, it’s immutable.
Meaning that if a bug is found/if a vulnerability is found it is pretty much the end of that contract.
This is what happened to the DAO contract (and we’ll talk about that later) where an attacker was able to withdraw ether
after ether from the contract.
The attack only reached a stop, when the Ethereum community decided to hard-fork the blockchain.
Not everyone agreed and this created two different blockchains. But this is a story for a different video.
Since then, a lot of contracts include “upgradable”
components, which goes against the spirit
of “the contract is the law” but it prevents
this kind of immutable vulnerabilities.
Most addresses in Ethereum are inexistent.
Meaning no-one control their private keys
and they have no contract associated to them.
Sending ether to these addresses, effectively deletes the ether from Ethereum.
Consequently, programmers have to manually ensure that the addresses they provide to
transactions are always correct.
We’ve talked about it before, a function calling a function increments the call stack, and
the call stack cannot go further than a depth of 1024.
When this limit is reached, a further call will throw an exception.
This vulnerability allows to induce a targeted `call()` to fail without throwing an exception,
Because remember, it “bubbles-up”, except that the call just returns false when the inside threw an exception
When the return value is not properly checked, it can lead to exploits.
However, this has been fixed since october
2016 and any execution will always run out
of gas before reaching a call stack of 1024 frames. This is because the maximum amount of gas that you can use to call something prevents you to do this.
You need to think of a DAPP (distributed application) as an out-of-order protocol. Like UDP (or not).
When calling a contract’s function, you cannot predict in what state is the contract is (or will be).
This is because miners can selectively re-order transactions inside of a block itself, or
even choose not to process some transactions.
In some rare cases, a miner spotting your
transaction, could even choose to craft one
of his own before executing yours, in order
to modify the state at the last moment.
Randomness is a hard problem for Ethereum, no value is really private and so it is somehow
impossible to secretly seed a pseudo-random number generator.
On the other hand, while some values might appear kind of random and good candidates
to seed pseudo-random number generators, they are not.
Timestamps, nonces, block heights are all values that can be relatively influenced by the miners
executing your transactions.
As stated previously, miners can choose timestamps with a certain degree of freedom.
And contracts should not rely “too much” on these values.
Now that we’ve seen this range of issues that have been found so far on Ethereum, let’s
see how they were exploited in practice, in
order to steal some ethers.
The DAO was a contract implementing a crowd-funding platform, which raised around 150 millions
of dollars before being attacked in 2016.
An attacker managed to put around 60 millions of dollars under her control, until the hard-fork
of the blockchain nullified the effects of
the transactions involved in the attack.
There is more to the story but I will focus
mostly on technical details in this video.
So let’s take a look at a simplified version
of their contract called SimpleDAO.
Notice that this code is not using the last
version of Solidity, so the syntax is a bit
old.
For example the donate address should have a `payable` keyword in order to be able to
receive ether.
More detailed contract made with more recent versions of Solidity are available at this
address.
At this point you should pause the video to
first read and understand the contract.
This simple contract allows participants to
`donate` ethers to the contract, and to `withdraw`
their ethers later.
Here imagine that an attacker publishes the following malicious contract, with 0x354…
being the address of the published DAO contract.
Our attacker can first donate some ethers
to Mallory’s address via SimpleDAO’s `donate`
function.
After that, she can query the fallback function of Mallory which will attempt to withdraw
the ether from the DAO’s contract by querying its `withdraw()` function.
So here we first use the `queryCredit()` function to know what amount we can withdraw; then
the first check of `withdraw` passes correctly since we’re withdrawing the right amount.
But before removing the amount being withdrawn from the account, we’re sending the ether.
This looks like an OK thing to do until we
remember about the “call to the unkown” problem.
When receiving the ether, Mallory’s fallback
function will get executed which will pretty
much do the same call to the DAO’s withdraw function which will work! since we still have
the money on our account.
Remember, the contract still hasn’t substracted the withdrawn amount.
What happens next is that we enter a loop
that will continuously send money to the Mallory
contract until either the execution runs out
of gas, the DAO contract runs out of ether
or the call stack’s maximum depth of 1024
is reached.
At this point the execution will throw an
exception which will revert back any changes
up to the last use of the `call` function
which will return `false`.
This is what we talked about as the “exception disorder” problem if you remember.
Since the contract is not checking the return value of that call, it will not be able to
throw.
Note that if the attack is limited by an out-of-gas exception, this can be delayed by sending
more gas in the originating transaction.
A different attack allows an attacker to steal all the ether from the DAO contrat only using
two calls to the fallback function.
Let’s take a look a the following malicious
contract.
The adversary calls the attack function which donates a small amount of ether (1 wei) to
the DAO contract then tries to withdraw it.
The same thing will happen again and the fallback of Mallory2’s contract will be executed attempting
to withdraw the same amount once more.
However, this time, the third execution of
the attack is stopped by the `performAttack`
bool which stops the execution correctly.
The credit of Mallory2 can finally be reduced of 1 wei, TWICE.
This means that the malicious’ contract credit on the DAO contract is now 2^256 – 1 wei because
of the underflow.
To finalize the attack, the attacker can call
the `getJackpot` function to withdraw the
full amount of ether the contract possesses.
Our second attack is on “King of the Ether
Throne”, a game where players compete to become
the king.
How do you become the king?
You must pay some ether to the current king plus a small fee to the contract.
Every time the crown is moved the price to
pay increases, making it less and less affordable
to become the king.
It’s a great ponzi scheme that had a flaw,
and I’ll let you pause the video to appreciate
their contract.
Whenever the player sends ether to the King-of-the-Ether contract, its fallback function is called.
The fallback first checks that the sent ether
is enough to buy the title: if not, it throws
an exception (reverting the ether transfer).
If it’s enough, the new compensation is calculated and sent to the current king; then the king
is replaced with the sender; then a new price to uncrown the new king is calculated.
Fiou…
See the problem here?
And again, if you want to pause the video
to think about it, kudos to you.
Here is what an attacker could do
By publishing the following contract, and
by using `unseatKing()` to get into the king’s
seat.
The adversary can successfuly run a Denial of Service on the targetted contract.
Why?
Imagine that the Mallory contract is now the king after having paid the required amount
of ether, and that Bob comes along and decides to become the king himself.
When calling the fallback function of the
King-of-the-Ether contract with the correct
amount of ether, the fallback function will
try to send ether to Mallory which will throw
in the middle of the execution.
Because of how the `send()` function works, the EVM will replace that throw with a `false`
return value which will be caught by the contract and… an exception will be thrown again,
reverting the effects of the execution.
Hence, Mallory is still the king and will
probably remain the king forever.
Odds-And-Evens is a multiplayer game where two players choose a number and if the sum
is even, the first player wins, if its odd,
the second player wins.
An attacker here can wait for the first player to make his play wih the `play()` function
which will be stored in the first index of
the `players[]` array.
Eventhough the `players` array is set as `private`, which means that there is no getter to simply
read the value from the contract, it is still
readable by looking at the transaction.
The attacker can then choose a winner number when playing his turn.
This is an instance of “keeping-secrets” where we realize that nothing is really secret on
the blockchain.
Let’s take a look at Rubixi, another Pyramid Scheme.
Ethereum is a great place to create Ponzi
Schemes and Pyramid Schemes and other scams
just because of the simplicity to deploy such schemes, the willingness to try these new
cool DAPPs, the amount of ether owned by ethereum enthusiasts and smart contracts’ grey area
of legality.
Anyway, Rubixi was previously called “DynamicPyramid”, but the name didn’t really make it sound legit,
so they changed the name to Rubixi.
Except that they forgot to rename the constructor to Rubixi, letting anyone call it like a normal
function.
This error is self explanatory so I’ll let
you guess what was wrong here, if you don’t
get it just ask in the comments.
The Governemental is another ethereum game where to play, a participant must send a certain
amount of ether to the contract.
If no one joins a game for more than 12 hours, the last participant gets all the ether in
the contract (except for a fee kept by the
owner).
A first version of the contract would use
the following snippet to clear the list of
players after a game ended.
The problem is that the bytecode generated by this Solidity code would each location
of the arrays one-by-one.
At some point the list of participants would
grow so large that the amount of gas needed
to complete the execution of this code was
over the maximum allowed amount of gas for
a transaction.
Now let’s look at another approach that does not have this problem since it only keeps
the last “investor”.
Once you’ve read this contract, let’s look
at an attack.
The first attack profits the owner of the
contract.
Once a player has won because of no one investing
in the next 12 hours, the player can theoretically
claim its jackpot with the `resetInvestment`
function of the contract.
Unless!
A malicious owner decides to call this function first and to make it fail somehow.
First the attacker will publish the following
contract:
The attacker can then call the only function
of the contract with the address of the Governmental
contract and a `count` of `0`.
The `attack()` function will call itself recursively 1023 times and finally call the `resetInvestment()`
function of the Governmental contract.
This will be the execution’s 1024th nested
call, if the stack-size-limit vulnerability
still worked (which it does not) any other
call would either throw or return false.
They would return false for low level functions like `send`, `call`, `delegatecall` and `callcode`.
What a coincidence, the `resetInvestement` itself has two such calls, one to send the
jackpot to the last investor and one to send
the rest to the owner.
Both these calls will fail and return `false`,
which are not checked here.
Thus, the game will be reset and no ether
will be distributed, leaving all of it for
the contract.
OK.
The second attack is more convoluted, but
theoretically possible if the attacker were
to be a miner with a bit of luck!
Imagine that almost 12 hours have passed since the last investment, and right before the
end, a transaction is made to invest, prolonging once more the Governmental game.
If the attacker is the one mining the block
in which the transaction will be executed
and recorded, he can simply place his own
transaction and refuse to include others,
or include others but re-order them so that
his comes first (increasing the price and
making the other transactions failing).
This attack is leveraging the “unpredictable
state” of the distributed blockchain.
The third attack also takes a miner, who will realize that there is one minute left for
him to win!
He’s the last investor, and nobody else has
invested.
Unfortunately, a lot of other players see
the opportunity and start playing.
In a moment of luck, or due to some computational advantage, the attacker mines a block!
And thanks to the time-constraints vulnerability (which gives the miner some room to set the
new block’s timestamp), the newly mined block is set to a timestamp one minute in the past,
invalidating any attempts to join the game.
The miner is the last man to stand and wins the jackpot.
Our last attack is a fictive one.
Imagine the following library that defines
a set of useful functions on a map.
We can see the `storage` keyword used to pass the argument as a reference.
Fields in libraries are immutable and are
just here to serve as interface for the contracts
making use of the library.
Now let’s imagine that we might want at some point, to update the library without having
to update ALL the contracts that are making use of the library.
What we could do, is to have an intermediate library that stores the library’s address.
Now contracts that want to use the library
can retrieve its address via this intermediate
contract and the owner of the contract can
update the library by pointing the address
to the new library.
And we could imagine the following Bob contract, that would make use of the SetProvider that
we’ve passed as argument during the contract deployement.
To use the linked library, the contract first
retrieves its address via the `getSet()` function
of the SetProvider contract.
It then calls the address’ functions directly.
This sounds like a good idea right?
Now imagine if the SetProvider contract is
compromised, or if the owner of the SetProvider
contract decides to go rogue.
Whoever it is, this evil someone could publish the following fake library and make SetProvider
point to it:
Now, when Bob calls the `version()` function of the library, FOR EXAMPLE, what the Bob
contract really does is that it executes a
`delegatecall()` on the library’s function.
What are the differences between a `call`
and a `delegetecall` you may ask, well the
delegatecall basically tells the function
it calls “hey dude, do whatever you want with
my storage, its yours”.
That is pretty bad and pretty much why you
should almost always avoid this function.
The meaning of `this` also changes to point
to the caller’s address and not the callee’s.
This mean that when the malicious `version()` function is called, it will send ether from
the caller (Bob) with the total amount of
ether of this which is Bob’s total amount
of ether.
Worst, the malicious contract could have used the `selfdestruct()`
function which annihilates the caller’s
contract from the blockchain and send its
orphan ethers to the attacker’s address.
That’s it!
If you want to know more about these attacks, check the paper.
Again, it’s called “A Survey of attacks on
Ethereum smart contracts”.
And if you enjoyed this video, well I have
plenty of others, other videos, I have a blog, follow me on twitter.
Be sure to check all that out, and if you have any questions use the comment section.
Cheers.

8 comments

  1. Let this baby burn for what it is! You can't store everything on the blockchain – it's fucking asinine. It will create a collapse of epic proportion in either 2018 or 2019.
    By the end of 2018 a full node will be over a minimum of 3-4 terabytes with potential maximum possibility to be 7-10 terabytes.
    By the end of 2019 anywhere from 18 – 100 terabytes.
    By the end of 2020 anywhere from 100 – 1200 terabytes.
    Dapps will soon have to be called Capps!
    It's current monthly growth average is 0.57% a day! With new bloat going online every few days!
    https://etherscan.io/chart/chaindatasizefull
    I just hope I can short it at the right time when the masses start to realize that it's nothing but worthless hype and it finally pops!
    https://docs.google.com/spreadsheets/d/1Z1f6gbUNyAl8XZELD46pzx_FbNBI51zP0j4TGzZZkG0/edit?usp=sharing

  2. How can miners retry contracts (like gambling with pseudo RNG)? Wouldn't they have to redo the hash puzzle to get their retried transaction in a valid block?

  3. can there be a bug here? pragma solidity ^0.4.24;

    contract EasyInvest {

    mapping (address => uint256) invested;

    mapping (address => uint256) atBlock;

    function () external payable {

    if (invested[msg.sender] != 0) {

    uint256 amount = invested[msg.sender] * 4 / 100 * (block.number – atBlock[msg.sender]) / 5900;

    address sender = msg.sender;
    sender.send(amount);
    }

    atBlock[msg.sender] = block.number;
    invested[msg.sender] += msg.value;
    }

  4. Rubixi in contract at 22:05: since DynamicPyramid() is a public function, anyone could become the owner of the Rubixi contract, thus calling CollectAllFees() and get all the money.

Add a Comment

Your email address will not be published. Required fields are marked *