Antipatterns and vulnerabilities

Antipatterns and why you care

So far, hundreds of millions of dollars have been stolen due to vulnerabilities in contracts. The situation is so bad that researchers can write papers about the flaws in these contract systems. Michelson has been designed to mitigate those issues as much as possible, but there are still mistakes you can make when designing Michelson contracts. This series of posts is about these vulnerabilities. Instead of telling you not to do things, I'm going to provide a vulnerable contract each Monday for the next month. Your job will be to spot the vulnerability. I'll be accessible on Riot or via email so you can check your answers. After a week, I'll post an explanation of the problem and how to avoid it.

October 16, 2017: Two vulnerabilities for the price of one!

The Contract

The below contract is meant to transfer 5.00 ꜩ to each of the two contracts in its storage. Let's assume that this contract was initialized with 100 ꜩ, which is meant to be split equally between the two contracts. There are two ways for the first person to deny the other person their fair share of the money. How can they do this?

parameter unit;
storage (pair (contract unit unit) (contract unit unit));
return unit;
code { CDR; DUP; CAR; PUSH tez "5.00"; UNIT;
       TRANSFER_TOKENS; DROP; DUP; CDR;
       PUSH tez "5.00"; UNIT; TRANSFER_TOKENS; PAIR };

The Vulnerability

Coming October 23.

October 9, 2017: Where could the money go?

The Contract

I've also launched this contract on the alphanet. Its address is TZ1ppjHeTTKeL16p46wnUBVbHu4qE6qqEHgg.

This contract is super simple:

parameter unit;
storage unit;
return unit;
code {}

Anyone can send money to it, but somehow, someone can take money from it. How?

The Vulnerability

The alphanet got reset on Friday, so here's the information you need to figure out what's going on:

This call will get us all of the information about the contract back as a JSON blob.

./alphanet.sh client rpc call /blocks/prevalidation/proto/context/contracts/TZ1ppjHeTTKeL16p46wnUBVbHu4qE6qqEHgg
{ "ok":
    { "manager": "tz1SuakBpFdG9b4twyfrSMqZzruxhpMeSrE5",
      "balance": 10000.000000, "spendable": true,
      "delegate": { "setable": false },
      "script":
        { "code":
            { "code": [], "argType": "unit", "retType": "unit",
              "storageType": "unit" },
          "storage": { "storage": "Unit", "storageType": "unit" } },
      "counter": 0.000000 } }

So, what's important in this block of JSON? The answer is the spendable field. If a contract has this field marked true, the manager of the contract can transfer funds out of the contract. Currently, this is the default when you originate a contract, but this will probably change before the launch. The take away is that a spendable contract has a single point of trust: the manager. Be wary of the spendable contracts.

October 2, 2017: What's in store

The Contract

This is the data publisher contract I wrote a month and a half ago. Unfortunately, it's got a vulnerability. I'll post the fixed version of this contract and reveal exactly how it can be attacked.

For those of you who don't remember this contract, users can get the value by paying a fee and the owner of a contract can sign a message that gives a new value for the contract to return when queried.

# NONE if user wants to get the value
# SOME (signed hash of the string, string)
parameter (option (pair signature string));
return string;
storage (pair key string);
code { DUP; DUP; CAR;
       IF_NONE { AMOUNT; PUSH tez "1.00"; # The fee I'm charging for queries
                 CMPLE; IF {} {FAIL};
                 CDR; DIP {CDDR}}
               { DUP; DIP{SWAP}; SWAP; CDAR;
                 DIP {DUP; CAR; DIP {CDR; H}; PAIR};
                 CHECK_SIGNATURE;
                 IF { CDR; SWAP; DIP{DUP}; CDAR; PAIR} {DROP; DUP; CDR; DIP{CDDR}}};
       SWAP; PAIR}

The Vulnerability

This contract has a replay vulnerability. Once the key is used to sign an update, it can be sent right back to the contract. We can fix the contract by doing the following:

# NONE if user wants to get the value
# SOME (signed hash of the string, string)
parameter (option (pair signature (pair string nat)));
return string;
storage (pair (pair key nat) string);
code { DUP; CAR; DIP{CDR; DUP};
       IF_NONE { AMOUNT; PUSH tez "1.00"; # The fee I'm charging for queries
                 CMPLE; IF {} {FAIL};
                 CDR; PAIR}
               { SWAP; DIP{DUP}; CAAR; DIP{DUP; CAR; DIP{CDR; H}; PAIR};
                 CHECK_SIGNATURE; ASSERT; DUP; CDDR; DIIP{DUP; CADR};
                 DIP{SWAP}; ASSERT_CMPEQ;
                 CDR; DUP; DIP{CAR; DIP{CAAR}}; CDR; PUSH nat 1; ADD;
                 DIP{SWAP}; SWAP; PAIR; PAIR; PUSH string ""; PAIR}}

September 25, 2017: Password Contract

The Contract

I really wanted the below contract to work because I, like many others in the world of cryptocurrency worry about losing my private key. This contract was meant to securely allow you to store your Tez in a password-protected contract. The contract below stores a salted hash of the password. When the user wants to access the funds, they send a transaction to the contract with the password and a destination contract. If the password is correct, the funds are sent to the destination.

Hint: Though this contract is potentially vulnerable to a dictionary attack, this is not the vulnerability I have in mind.

parameter (pair string (contract unit unit));
return unit;
storage (pair string string);
code { DUP; CAR; DIP { CDR; DUP}; DUP; CDR;                        # Unpack data
       DIP { CAR; DIP{DUP; CAR}; SWAP; PAIR; H; DIP{CDR}; CMPEQ }; # Check salted password
       SWAP; ASSERT; BALANCE; PUSH tez "5.00"; SWAP; SUB; UNIT;    # Setup transfer
       TRANSFER_TOKENS; PAIR}                                      # Make transfer and finish

The Vulnerability

The answer is that the P2P nature of the blockchain breaks this contract. In order to have your transaction included in the blockchain, you need to broadcast your transaction to Tezos' gossip protocol. Once this information has been broadcast, a malicious node can read the password from your transaction. Then, they can create a new transaction with your password and steal your money.

What's the lesson here? If you send it to the blockchain in a readable format, your data is public. This goes for information stored in a contract's storage and data broadcast as a transaction. You can verify the ownership of a message by checking the source of a transaction or asking a user to sign it first. However, the purpose of this contract is to avoid these mechanisms, so without a more powerful mechanism, this contract cannot be fixed.