Background
Here is a write up of a technical assessment for an offensive security position specializing in Blockchain audit. I was given 3 assessments but I only managed to solve 2 out of the 3 assessments. The first one was a straight up host & Web CTF which I didnt have much problems but I don't plan to release the solutions yet. This write up is for the 2nd assessment. Unfortunately, the 3rd one involved Rust coding which I did not have enough skills to solve & I threw in the towel on that challenge. I am releasing the 2nd assessment solution since this one was already made obsolete by the interviewer. The idea is to exploit a Reentry bug in the smart contract to steal Eth tokens from victims accounts.
Blockchain audits are still considered a niche skillset, hence my hope that it could help others out there pursuing this career path.
The exploit PoC can be found at my github repo here.
The smart contract code (Private-Sale.sol) can be found here. Find the Reentry bug and drain the victims accounts.
Steps
The following are steps to deploy the SmartContracts into Ganache environment and exploit the Reentry vulnerability as highlighted during the static analysis.
Connect Remix to Ganache and deploy Contract to Web3 Provider
Enter the bankAddress to the Attacker.sol exploit and deploy.
Transfer some Ether to the bankAddress, make sure to also transfer some Ether from the attacker address to BankAddress. This can be done by clicking on the buyTickets in the attacker contract.
Click on “attack” and the bank balance should be 0.
Edit the Attacker.sol exploit and add the attacker ether address into function balance2() and
redeploy it and click on “Balance2” to see attacker had now obtained all the Ethers.
The full exploit code - Attacker.sol
Further Analysis
1. In MyToken.sol did you find something related to the centralization of the contract (explain your answer)?
If the answer is yes, how could you solve it? (at least two answers)
Yes, there is some centralization of this contract. For eg. in the MyToken.sol, with the exception of the EmergencyDestroy(),
only the contract owner can pause and unpause the contract, see lines no 16 - 22:
Assuming that the pause and unpause functions are used for troubleshooting, this also means that the contract owner
has too much control of this contract and in cases of disputes, only the contract owner can resolve it. Also, looking at lines no 27 - 29,
it seems to me that the contract owner can transfer all Ethers from the contract when the contract is paused!
To solve this:
(i) Apply the usage of Multi signature wallets to this contract that allow multiple signers to review and agree on an action on the blockchain
before an action is executed.
(ii) Apply a function Modifier to control who can call the EmergencyDestroy() function on lines no 30 - 32.
2. In the same token: Why people used in the past selfdestruct function? Why people don’t use anymore?
Which alternatives people have now for solving the problem?
The selfdestruct function was used to “to destroy a contract on the blockchain system”. It enabled programmers to remove smart contracts
from Ethereum and transfer Ethereums during emergencies e.g. when being attacked by Reentry attacks. However, “This selfdestruct function
is however a double-edged sword for developers. On the one hand, the function enables contract owners have the ability to reduce financial loss when
emergency situations happen. On the other hand, this function is also harmful. The function might open an attack vector for attackers. It may also lead to a trust
concern from the contract users, as the contract owners can transfer user’s Ethers that are stored on the contract”
Source of answer: https://xin-xia.github.io/publication/tosem216.pdf
In MyTokens.sol lines no 30 - 33 slither identified 1 (one) instance of suicidal vulnerability. The function - EmergencyDestroy() was declared
as a public function, this method received a value to a payable address declared as “_to”.
Due to improper access control applied to this method, anyone can call it and force the sending of Ethers to another contract address
because the “_to” is user controllable.
Possible recommended fixes:
(i). “Consider removing the self-destruct functionality unless it is absolutely required. If there is a valid use-case, it is
recommended to implement a multisig scheme so that multiple parties must approve the self-destruct action.
” https://swcregistry.io/docs/SWC-106.
(ii). “six suggestions about how to better use Selfdesturct function” taken from page 27 of https://xin-xia.github.io/publication/tosem216.pdf”
Suggestion 1. Limit Usage Scenario
Suggestion 2. Permission Check
Suggestion 3. Distribute the Rights and Modularization
Suggestion 4. Delay Self-destruct Action
Suggestion 5. Pause Functionality
Suggestion 6. Refund Values
3. Did you find something related to rugpulls in all contracts?
The following contracts had vulnerabilities that could have been deliberately introduced to facilitate rug pulls:
(i). Private-Sale.sol - Reentry bug allows an attacker to drain the Ethers from the contract. As demonstrated in my earlier PoC exploit.
(ii). HalbornPool.sol - Authorization through “tx.origin” in line no 22 that allows the attacker to withdraw all Ethers from the contract.
(iii). MyToken.sol - The overly permissive functions in lines no 27 - 29 granted to Contract owner when the contract
is paused allows him to transfer all Ethers from the contract.
4. In HalbornPool, did you find something weird related to modifiers?
Line no 22 in the modifier function OnlyOwner() defines the “msg.sender” equals the “tx.origin”.
As per SWC-115, using the tx.origin “could make a contract vulnerable if an authorized account calls into a malicious contract.
A call could be made to the vulnerable contract that passes the authorization check since tx.origin returns the original sender of the transaction
which in this case is the authorized account” source: https://swcregistry.io/docs/SWC-115
Due to this vulnerability, an attacker can call the EmergencyWithraw() function at line no 131 to move the balance out from
this smart contract address.
Possible fix - the HalbornPool.sol contract should use the following code to correct this mistake on line no 22:
require(msg.sender == owner);
I generally check this kind of blog and I found your blog which is related to my interest. Genuinely, it is good and instructive information about Scalable Ethereum Software To Validate Data Thanks for sharing an amazing blog here.
ReplyDelete