pragmasolidity ^0.4.21;contract AssumeOwnershipChallenge {address owner;boolpublic isComplete;functionAssumeOwmershipChallenge() public { owner = msg.sender; }functionauthenticate() public {require(msg.sender == owner); isComplete =true; }}
Note that AssumeOwmershipChallenge() has a typo, so it is declared as a function instead of the constructor. Since this function is public, anyone can call it and reset the owner.
Solution
Copy and paste the challenge contract to Remix and interact with the instance via "At Address". Call AssumeOwmershipChallenge(), then call authenticate().
Token bank
Code Audit
pragmasolidity ^0.4.21;interface ITokenReceiver {functiontokenFallback(address from,uint256 value,bytes data) external;}contract SimpleERC223Token {// Track how many tokens are owned by each address.mapping (address=>uint256) public balanceOf;stringpublic name ="Simple ERC223 Token";stringpublic symbol ="SET";uint8public decimals =18;uint256public totalSupply =1000000* (uint256(10) ** decimals);eventTransfer(addressindexed from, addressindexed to, uint256 value);functionSimpleERC223Token() public { balanceOf[msg.sender] = totalSupply;emitTransfer(address(0), msg.sender, totalSupply); }functionisContract(address_addr) privateviewreturns (bool is_contract) {uint length;assembly {//retrieve the size of the code on target address, this needs assembly length :=extcodesize(_addr) }return length >0; }functiontransfer(address to,uint256 value) publicreturns (bool success) {bytesmemory empty;returntransfer(to, value, empty); }functiontransfer(address to,uint256 value,bytes data) publicreturns (bool) {require(balanceOf[msg.sender] >= value); balanceOf[msg.sender] -= value; balanceOf[to] += value;emitTransfer(msg.sender, to, value);if (isContract(to)) {ITokenReceiver(to).tokenFallback(msg.sender, value, data); }returntrue; }eventApproval(addressindexed owner, addressindexed spender, uint256 value);mapping(address=>mapping(address=>uint256)) public allowance;functionapprove(address spender,uint256 value)publicreturns (bool success) { allowance[msg.sender][spender] = value;emitApproval(msg.sender, spender, value);returntrue; }functiontransferFrom(address from,address to,uint256 value)publicreturns (bool success) {require(value <= balanceOf[from]);require(value <= allowance[from][msg.sender]); balanceOf[from] -= value; balanceOf[to] += value; allowance[from][msg.sender] -= value;emitTransfer(from, to, value);returntrue; }}contract TokenBankChallenge { SimpleERC223Token public token;mapping(address=>uint256) public balanceOf;functionTokenBankChallenge(address player) public { token =newSimpleERC223Token();// Divide up the 1,000,000 tokens, which are all initially assigned to// the token contract's creator (this contract). balanceOf[msg.sender] =500000*10**18; // half for me balanceOf[player] =500000*10**18; // half for you }functionisComplete() publicviewreturns (bool) {return token.balanceOf(this) ==0; }functiontokenFallback(address from,uint256 value,bytes) public {require(msg.sender ==address(token));require(balanceOf[from] + value >= balanceOf[from]); balanceOf[from] += value; }functionwithdraw(uint256 amount) public {require(balanceOf[msg.sender] >= amount);require(token.transfer(msg.sender, amount)); balanceOf[msg.sender] -= amount; }}
Look at the withdraw() function:
functionwithdraw(uint256 amount) public {require(balanceOf[msg.sender] >= amount);require(token.transfer(msg.sender, amount)); balanceOf[msg.sender] -= amount;}
This function violates the "checks-effects-interactions" pattern, hence vulnerable to reentrancy attack. When token.transfer(msg.sender, amount) is called, the control flow goes into msg.sender's fallback function. If we call challenge.withdraw() again inside the fallback flow, the control flow will "re-enter"challenge.withdraw(). That is, TokenBankChallenge.withdraw() can be called many many times until all the fund in that contract is drained.
ERC223 is a superset of ERC20. The difference is ERC-223 "notifies" the recipient of a transfer by calling the recipient's tokenFallback function in case the recipient is a contract. This logic was implemented in token.transfer():
In this challenge, we will be using tokenFallback instead of the standard fallback function when building reentrancy attack exploit due to the design of ERC223. Besides this, the attack will be same as a standard reentrancy attack.
Pay attention to the challenge.withdraw() function:
Note that the msg.sender here will be the exploit contract we just deployed. In order to pass this requirement, we need at least 500000000000000000000000 tokens in the exploit contract account. Since the initial balance of the exploit contract account is 0 and the initial balance of the player account is 500000000000000000000000, we can 1) convert all balances to tokens for the player account and 2) transfer all tokens from the player account to the exploit contract account, then 3) convert those tokens back to balance for the exploit contract account.
To exploit this challenge, do the following things in order:
Step 0: Interact with the challenge contract via "At Address". Get the token address via the getter challenge.token(). Interact with the token contract via "At Address".
Step 1 (manually): Call challenge.withdraw(500000000000000000000000) to convert all balances to tokens. Now the player account has 500000000000000000000000 tokens.
Step 2 (manually): Call token.approve(<exploit_contract_address>, 500000000000000000000000). This step prepares for token.transforFrom().
Step 3: Call stepThree(<your_Metamask_wallet_address>). This will transfer all the tokens from the player account to the exploit contract account.
Step 4: Call stepFour(). This will transfer all the token from the exploit contract account to the challenge contract account. The challenge.tokenFallback() function will be triggered, so challenge.balanceOf(<exploit_contract_address>) == 500000000000000000000000 now.
Step 5: Call tokenFallback(0x0000000000000000000000000000000000000000,1337,0x1337). This will trigger the reentrancy attack. Just pass in some junk parameters since they don't matter.
Step 6: Call challenge.isComplete() to verify if the reentrancy attack succeeded.
Here step 1 and step 2 are done manually because they are involved with msg.sender, which is supposed to be our Metamask wallet address.