At 9/13/2019 the EOSPlay DApp was hacked. The hacker exploited a flaw of the implementation of the EOSplay Random Number Generator (RNG), which allows him to take away about 30,000 EOS from the EOSPlay smart contract.
NOTE 1: The attack is not related to the design of EOSIO but only to the design of this particular DApp.
NOTE 2: We do not have the source codes of the hacked contract. Everything described on this article is a set of assumptions made by smart-contract developers based on what we know about the smart-contracts and EOS. We can only observe the history of transactions at the time of the attack and, based on the results of these transactions, draw conclusions about the attack principle, design of the attacker’s contract and contract of attacked DApp.
If you found a mistake, something that I’m missing or any useful info that I should include into this article please contact me: https://t.me/Dexaran
Here are the results of the contract decompilation:
How it happens?
What attacker did
- Rented 1,2M of CPU and NET at EOSREX resource exchange. (This costs him ~300 EOS)
- Staked CPU&NET for himself, eosplayadmin and dswinnermach. (I’m not sure but it seems that eosplayadmin belongs to the attacker while dswinnermach is listed as Spinach DApp at the DappRadar)
- The attacker created a lot of deferred transactions to the attacked EOSPlay DApp and won a lot of EOS (about 30,000). Deferred transactions caused network congestion and prevented most users from sending transactions.
- Four hours later the attacker caused a new wave of network congestion. At that time he was sending transactions to dswinnermach. (For example this one)
- The attack resolves. Network congestion stopped. EOS drained by the attacker is currently located here.
Apparently EOSPlay’s RNG was built fully on-chain. The problem with the RNG is that it requires a source of entropy to generate a random number. There is no built-in source of entropy in EOS. EOSPlay developers decided to use future blocks as a source of entropy, because the user does not know what will happen in the future at the time of sending the transaction.
It turns out that the hacker could manipulate future transactions to send “losing” transactions into infinite loops, thus preventing them from being executed. ”Winning” transactions were left to be executed successfully.
There is a REX (resource exchange) in EOS. REX enables users to buy CPU and NET bandwidth and use it to power transactions.
The attacker fills the queue with deferred transactions and waited until they are in the block where the bet is executed. Then he sent “losing” transaction into infinite loops. (Source: Michael Yeates)
These are hacker’s accounts:
https://eosflare.io/account/mumachayinmm https://eosflare.io/account/gotoworkhome https://eosflare.io/account/mumachayinm1 https://eosflare.io/account/mumachayinm2 https://eosflare.io/account/mumachayinm3
What do we know?
Here are Daniel Larimer’s comments regarding the attack (source):
Owning and staking #eos gives users a prorata share of available bandwidth. When people don’t use their share it is redirected to others on a prorata basis. During heavy use users no longer receive this free benefit.
Lesson learned here is don’t design contracts that depend upon extra bandwidth available during uncontested mode. The eosplay contract should have a low cpu action to pause execution available to contract maintainers.
There were some statements that the attacker made it impossible for EOSPlay developers to stop the contract and interrupt the attack. For example this article (EOSGO) states the following:
Since the attacker caused the EOS network to become congested, the EOSPlay team was not able to block its smart contract operations. Therefore, the attacker managed to completely empty EOSPlay’s funds.
This is not true from what I know. The developers stopped the contract. Here is the transaction: https://eosflare.io/tx/2E32E80D77571D639535A830BA772A2D6F9FBAEB90A510CC5ACA6406A5EE1764
The EOSPlay conract was not emptied. Currently there are 148833.86 EOS remaining at the account’s balance: https://eosflare.io/account/eosplaybrand
What lesson should we learn from this?
- Smart-contract security is important. The fact that you can stop/upgrade the contract does not mean that you are 100% secure. It’s smart-contract developers’ responsibility to conduct security audits, but bounties and all the security-related operations to make ensure smart-contracts security. It’s way better to spend $20K on a professional security audit rather than have an exploit which will allow hackers to steal $100K.
- Random Number Generators must not use on-chain sources of entropy to generate numbers. There is no viable source of entropy in EOS blockchain.
- It is necessary to have a spare account staked with CPU and a super-cheap `stopcontract()` function in your DApp contract. Even if your contract is stoppable/upgradeable it does not mean that you can stop or redeploy it if someone will initiate a harsh network congestion. Every attacker can rent a lot of CPU via REX and initiate a congestion before exploiting some bugs in a DApp. If DApp developers will not have an account that will have enough CPU staked to withstand a congestion and keep the ability to send transactions then DApp developers will not be able to access the contract during the attack.
- Automated anomalies detection is also worth considering. When designing your DApp it is better to think about possible attack scenarios and implement some checks in advance. For example if your gambling DApp is going to pay players 30 times in a row then it is a bad sign. The problem here is out lack of imagination and the inability to predict all possible scenarios. If a hacker is a bit more creative than a developer of a DApp then the attack may come from a direction we never expect.
- It is better to have source codes published to enable community review the code and highlight the vulnerabilities. In case of a real attack it will be easier to investigate and understand it. This will help to avoid such incidents in future.
What we know about the attackers contract?
The contract is located here: https://eosauthority.com/account?account=mumachayinmm
It uses 530 kb RAM so it is a decent contract. Not 10 lines of code.
There are the following public actions at the contract:
- setc(currentvc, usedc, taskc, currentb)
- settarget(t1, t2)
- run(num, id) — the main function of the contract that was used to send deferred transactions
- start(num, id)
There are three public tables at the contract:
- kkks — preserves a single row: id = 0 and flag = 0
- infos — preserves a single row: id=0, currentvc = 1, usedc = 10, taskc = 0, currentb = 0
- configs — preserves a single row: id=0, amount=0, flag=0, betnum=0, targettime= 1568418945500, x1= 450000, x2= 400000
Here you can find the contracts’ ABI: https://gist.github.com/Dexaran/e7f98e19f31077b549024b464cf45258